/**
* BlueCove - Java library for Bluetooth
* Copyright (C) 2006-2009 Vlad Skarzhevskyy
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @author vlads
* @version $Id$
*/
package net.sf.bluecove;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.UUID;
import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import org.bluecove.tester.log.Logger;
import org.bluecove.tester.util.IOUtils;
import org.bluecove.tester.util.RuntimeDetect;
import org.bluecove.tester.util.StringUtils;
import org.bluecove.tester.util.TimeUtils;
import net.sf.bluecove.se.JavaSECommon;
import net.sf.bluecove.util.BluetoothTypesInfo;
import net.sf.bluecove.util.CollectionUtils;
import net.sf.bluecove.util.CountStatistic;
import net.sf.bluecove.util.IntVar;
import net.sf.bluecove.util.TimeStatistic;
/**
*
*/
public class TestResponderClient extends TestResponderCommon implements Runnable, CanShutdown {
public static int countSuccess = 0;
public static FailureLog failure = new FailureLog("Client failure");
public static int discoveryCount = 0;
public static int connectionCount = 0;
public static int discoveryDryCount = 0;
public static int discoverySuccessCount = 0;
public static long lastSuccessfulDiscovery;
public static int countConnectionThreads = 0;
private int connectedConnectionsExpect = 1;
private int connectionLogPrefixLength = 0;
private int connectedConnectionsInfo = 1;
private Vector concurrentConnections = new Vector();
public static CountStatistic concurrentStatistic = new CountStatistic();
public static TimeStatistic connectionDuration = new TimeStatistic();
public static CountStatistic connectionRetyStatistic = new CountStatistic();
public Thread thread;
private Object threadLocalBluetoothStack;
boolean stoped = false;
TestClientConfig config;
boolean isRunning = false;
boolean runStressTest = false;
TestClientBluetoothInquirer bluetoothInquirer;
public static Hashtable recentDeviceNames = new Hashtable/* <BTAddress,Name> */();
boolean configured = false;
static int sdAttrRetrievableMax = 255;
public static synchronized void clear() {
countSuccess = 0;
failure.clear();
discoveryCount = 0;
concurrentStatistic.clear();
connectionDuration.clear();
}
public TestResponderClient() throws BluetoothStateException {
this(true);
}
public TestResponderClient(boolean logLocalDevice) throws BluetoothStateException {
config = new TestClientConfig();
if (logLocalDevice) {
TestResponderCommon.startLocalDevice();
}
threadLocalBluetoothStack = Configuration.threadLocalBluetoothStack;
String v = LocalDevice.getProperty("bluetooth.sd.attr.retrievable.max");
if (v != null) {
sdAttrRetrievableMax = Integer.valueOf(v).intValue();
if ((sdAttrRetrievableMax > 7) && (RuntimeDetect.isJ2ME)) {
sdAttrRetrievableMax = 7;
}
}
}
static boolean isMultiProtocol() {
return ((Configuration.supportL2CAP) && Configuration.testL2CAP.booleanValue() && Configuration.testRFCOMM
.booleanValue());
}
public void connectAndTest(String serverURL, String urlArgs, IntVar firstCase, IntVar lastCase,
TestResponderClientConnection connectionHandler) {
String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress(serverURL);
String deviceName = niceDeviceName(deviceAddress);
long start = System.currentTimeMillis();
Logger.debug("connect:" + deviceName + " " + serverURL);
String logPrefix = "";
if (isMultiProtocol()) {
deviceName = connectionHandler.protocolID() + " " + deviceName;
}
if (connectedConnectionsExpect > 1) {
logPrefix = "[" + StringUtils.padRight(deviceName, connectionLogPrefixLength, ' ') + "] ";
}
for (int testType = firstCase.getValue(); (!stoped) && (runStressTest || testType <= lastCase.getValue()); testType++) {
Connection conn = null;
ConnectionHolder c = null;
TestStatus testStatus = new TestStatus();
testStatus.pairBTAddress = deviceAddress;
TestTimeOutMonitor monitor = null;
long connectionStartTime = 0;
try {
if (!runStressTest) {
Logger.debug(logPrefix + "test #" + testType + " connects");
} else {
testType = Configuration.STERSS_TEST_CASE.getValue();
}
int connectionOpenTry = 0;
while ((conn == null) && (!stoped)) {
try {
conn = Connector.open(serverURL + urlArgs, Connector.READ_WRITE, true);
} catch (IOException e) {
connectionOpenTry++;
if ((stoped) || (connectionOpenTry > CommunicationTester.clientConnectionOpenRetry)) {
throw e;
}
Logger.debug(logPrefix + "Connector error", e);
Thread.sleep(Configuration.clientSleepOnConnectionRetry);
Logger.debug(logPrefix + "connect retry:" + connectionOpenTry);
String cCount = LocalDevice.getProperty("bluecove.connections");
if ((cCount != null) && (!"0".equals(cCount))) {
Logger.debug(logPrefix + "has connections:" + cCount);
}
}
}
if (stoped) {
return;
}
c = connectionHandler.connected(conn);
connectionStartTime = System.currentTimeMillis();
connectionRetyStatistic.add(connectionOpenTry);
connectionCount++;
c.registerConcurrent(concurrentConnections);
c.concurrentNotify();
if (connectedConnectionsInfo < c.concurrentCount) {
connectedConnectionsInfo = c.concurrentCount;
Logger.info(logPrefix + "now connected:" + connectedConnectionsInfo);
synchronized (TestResponderClient.this) {
TestResponderClient.this.notifyAll();
}
}
c.active();
monitor = TestTimeOutMonitor.create(logPrefix + "test #" + testType, c,
Configuration.clientTestTimeOutSec);
if (!runStressTest) {
Logger.debug(logPrefix + "run test #" + testType);
} else {
Logger.debug(logPrefix + "connected:" + connectionCount);
if (connectionCount % 5 == 0) {
Logger.debug("Test time " + TimeUtils.secSince(start));
}
}
if (testType > Consts.TEST_SERVER_TERMINATE) {
Configuration.setLastServerURL(serverURL);
}
connectionHandler.executeTest(testType, testStatus);
if (monitor.isShutdownCalled()) {
failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName()
+ " terminated by by TimeOut");
} else if (testStatus.isError) {
failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName());
} else if (testStatus.isSuccess) {
countSuccess++;
Logger.debug(logPrefix + "test #" + testType + " " + testStatus.getName() + ": OK");
} else if (testStatus.streamClosed) {
Logger.debug(logPrefix + "see server log");
} else {
connectionHandler.replySuccess(logPrefix, testType, testStatus);
countSuccess++;
Logger.debug(logPrefix + "test #" + testType + " " + testStatus.getName() + ": OK");
}
if (connectionCount % 5 == 0) {
Logger.info("*Success:" + countSuccess + " Failure:" + failure.countFailure);
}
Configuration.setLastServerURL(serverURL);
// Delay to see if many connections are made.
if ((connectedConnectionsExpect > 1) && (connectedConnectionsInfo < connectedConnectionsExpect)) {
synchronized (TestResponderClient.this) {
try {
TestResponderClient.this.wait(3 * 1000);
} catch (InterruptedException e) {
break;
}
}
Logger.debug(logPrefix + "concurrentCount " + c.concurrentCount);
}
} catch (Throwable e) {
if (!stoped) {
if ((monitor != null) && (monitor.isShutdownCalled())) {
failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName()
+ " terminated by by TimeOut");
} else if (testType < Consts.TEST_SERVER_TERMINATE) {
failure.addFailure(deviceName + " test #" + testType + " " + testStatus.getName(), e);
}
}
Logger.error(deviceName + " test #" + testType + " " + testStatus.getName(), e);
} finally {
if (connectionStartTime != 0) {
connectionDuration.add(TimeUtils.since(connectionStartTime));
}
if (monitor != null) {
monitor.finish();
}
if (c != null) {
c.disconnected();
if (c.concurrentCount != 0) {
concurrentStatistic.add(c.concurrentCount);
}
c.shutdown();
} else {
IOUtils.closeQuietly(conn);
}
}
if ((Configuration.clientTestStopOnErrorCount != 0)
&& (failure.countFailure >= Configuration.clientTestStopOnErrorCount)) {
shutdown();
}
// Let the server restart
if (!stoped) {
try {
Thread.sleep(Configuration.clientSleepBetweenConnections);
} catch (InterruptedException e) {
break;
}
}
}
if (!Configuration.clientContinuous.booleanValue()) {
connectionHandler.sendStopServerCmd(serverURL);
}
}
private boolean connectAndTest(String serverURL) {
if (BluetoothTypesInfo.isRFCOMM(serverURL)) {
if (Configuration.testRFCOMM.booleanValue()) {
TestResponderClientRFCOMM.connectAndTest(this, serverURL);
return true;
}
} else if (BluetoothTypesInfo.isL2CAP(serverURL)) {
if (Configuration.supportL2CAP) {
if (Configuration.testL2CAP.booleanValue()) {
TestResponderClientL2CAP.connectAndTest(this, serverURL);
return true;
}
} else {
Logger.warn("Can't test L2CAP on this stack");
}
} else {
Logger.warn("No tests for connection type " + serverURL);
}
return false;
}
private class ClientConnectionTread extends Thread {
String url;
boolean started;
ClientConnectionTread(String url) {
// CLDC_1_0 super("ClientConnectionTread" +
// (++countConnectionThreads));
++countConnectionThreads;
this.url = url;
}
public void run() {
RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
started = connectAndTest(url);
}
}
private void connectAndTest(Vector urls) {
int numberOfURLs = urls.size();
if ((!Configuration.clientTestConnectionsMultipleThreads) || (numberOfURLs == 1)) {
connectedConnectionsExpect = 1;
connectedConnectionsInfo = 1;
for (Enumeration en = urls.elements(); en.hasMoreElements();) {
if (stoped) {
break;
}
String url = (String) en.nextElement();
connectAndTest(url);
}
} else {
connectedConnectionsExpect = numberOfURLs;
connectedConnectionsInfo = 1;
connectionLogPrefixLength = 0;
for (Enumeration en = urls.elements(); en.hasMoreElements();) {
String deviceAddress = BluetoothTypesInfo.extractBluetoothAddress((String) en.nextElement());
String deviceName = niceDeviceName(deviceAddress);
if (deviceName.length() > connectionLogPrefixLength) {
connectionLogPrefixLength = deviceName.length();
}
}
if (isMultiProtocol()) {
connectionLogPrefixLength += 3;
}
Logger.debug("start " + numberOfURLs + " threads");
Vector threads = new Vector();
for (Enumeration en = urls.elements(); en.hasMoreElements();) {
ClientConnectionTread t = new ClientConnectionTread((String) en.nextElement());
t.start();
threads.addElement(t);
}
int connectedConnectionsStartedExpect = 0;
for (Enumeration en = threads.elements(); en.hasMoreElements();) {
ClientConnectionTread t = (ClientConnectionTread) en.nextElement();
if (t.started) {
connectedConnectionsStartedExpect++;
}
try {
t.join();
} catch (InterruptedException e) {
break;
}
}
if (connectedConnectionsInfo < connectedConnectionsStartedExpect) {
if (!stoped) {
failure.addFailure("Fails to establish " + connectedConnectionsStartedExpect
+ " connections same time");
Logger.error("Fails to establish " + connectedConnectionsStartedExpect + " connections same time");
}
} else {
if (connectedConnectionsInfo > 1) {
Logger.info("Established " + connectedConnectionsInfo + " connections same time");
}
}
}
}
public void configured() {
synchronized (this) {
configured = true;
this.notifyAll();
}
}
public long lastActivityTime() {
return lastSuccessfulDiscovery;
}
public void run() {
synchronized (this) {
while (!configured) {
try {
this.wait();
} catch (InterruptedException e) {
return;
}
}
}
RuntimeDetect.cldcStub.setThreadLocalBluetoothStack(threadLocalBluetoothStack);
Logger.debug(config.logID + "Client started..." + TimeUtils.timeNowToString());
isRunning = true;
try {
bluetoothInquirer = new TestClientBluetoothInquirer(config);
Switcher.clientStarted(this);
int startTry = 0;
if (config.connectURL != null) {
if (!config.connectURL.equals("")) {
bluetoothInquirer.serverURLs.addElement(config.connectURL);
}
} else if (config.connectDevice != null) {
Configuration.clientContinuousDiscoveryDevices = false;
bluetoothInquirer.devices.addElement(new RemoteDeviceIheritance(config.connectDevice));
}
while (!stoped) {
if ((config.connectURL != null) && (config.connectURL.equals(""))) {
try {
bluetoothInquirer.serverURLs.removeAllElements();
DiscoveryAgent discoveryAgent = LocalDevice.getLocalDevice().getDiscoveryAgent();
UUID uuid = (config.searchOnlyBluecoveUuid) ? Configuration.blueCoveUUID()
: Configuration.discoveryUUID;
String url = discoveryAgent.selectService(uuid, Configuration.getRequiredSecurity(), false);
if (url != null) {
Logger.debug(config.logID + "selectService service found " + url);
bluetoothInquirer.serverURLs.addElement(url);
} else {
Logger.debug(config.logID + "selectService service not found");
}
} catch (BluetoothStateException e) {
Logger.error(config.logID + "Cannot selectService", e);
}
} else if ((!bluetoothInquirer.hasServers())
|| (Configuration.clientContinuousDiscovery.booleanValue() && (config.connectURL == null))
|| (!Configuration.clientTestConnections)) {
if (!bluetoothInquirer.runDeviceInquiry()) {
if (stoped) {
break;
}
startTry++;
try {
Thread.sleep(Configuration.clientSleepOnDeviceInquiryError);
} catch (Exception e) {
break;
}
if (startTry < 3) {
continue;
}
Switcher.yield(this);
} else {
startTry = 0;
}
while (bluetoothInquirer.inquiring) {
try {
Thread.sleep(1000);
} catch (Exception e) {
break;
}
}
}
if ((Configuration.clientTestConnections) && (bluetoothInquirer.hasServers())) {
discoveryDryCount = 0;
discoverySuccessCount++;
lastSuccessfulDiscovery = System.currentTimeMillis();
if (!config.discoveryOnce) {
connectAndTest(bluetoothInquirer.serverURLs);
}
} else {
discoveryDryCount++;
if ((discoveryDryCount % 5 == 0) && (lastSuccessfulDiscovery != 0)) {
Logger.debug(config.logID + "No services " + discoveryDryCount + " times for "
+ TimeUtils.secSince(lastSuccessfulDiscovery) + " " + discoverySuccessCount);
}
}
Logger.info(config.logID + "*Success:" + countSuccess + " Failure:" + failure.countFailure);
if ((countSuccess + failure.countFailure > 0) && (!Configuration.clientContinuous.booleanValue())) {
break;
}
if (stoped || config.discoveryOnce || config.connectOnce) {
break;
}
Switcher.yield(this);
}
} catch (Throwable e) {
if (!stoped) {
Logger.error(config.logID + "client error ", e);
}
} finally {
Switcher.clientEnds(this);
config.connectURL = null;
isRunning = false;
Logger.info(config.logID + "Client finished! " + TimeUtils.timeNowToString());
Switcher.yield(this);
}
}
public boolean isAnyServiceFound() {
if (bluetoothInquirer != null) {
return bluetoothInquirer.anyServicesFound;
} else {
return false;
}
}
public void shutdown() {
Logger.info(config.logID + "shutdownClient");
stoped = true;
if (bluetoothInquirer != null) {
bluetoothInquirer.shutdown();
}
if (RuntimeDetect.cldcStub != null) {
RuntimeDetect.cldcStub.interruptThread(thread);
}
Vector concurrentConnectionsCopy = CollectionUtils.copy(concurrentConnections);
for (Enumeration iter = concurrentConnectionsCopy.elements(); iter.hasMoreElements();) {
ConnectionHolder t = (ConnectionHolder) iter.nextElement();
t.shutdown();
}
}
}