/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.jsr082.bluetooth; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; /* * This abstract class provides base functionality for all SDP * transactions. */ public abstract class SDPClientTransaction extends SDPClientTransactionBase implements Runnable { protected static final boolean DEBUG = false; /* PDU ID (see Bluetooth Specification 1.2 Vol 3 page 131) */ byte pduID; /* Transcation ID used to identify this transaction. */ int ssTransID; /* Effective transaction ID. */ int pduTransID; /* Length of all parameters. */ long parameterLength; /* Continuation state used with partial responses. */ byte[] continuationState = null; /* Listener to report request result to. */ SDPResponseListener listener; /* Maps transaction IDs to ServiceTransaction objects. */ private static Hashtable transactions = new Hashtable(); protected JavaSDPClient client = null; public static SDPClientTransaction findTransaction( int pduTransactionID ) { SDPClientTransaction result = null; if (pduTransactionID > 0) { synchronized (transactions) { result = (SDPClientTransaction)transactions.get( new Integer( pduTransactionID ) ); } } return result; } /* * Cancels all current transactions. Called in case of I/O failure * in the underlying L2CAP connection. */ public static void cancelAll(int reason) { // force actual release of resources synchronized (transactions) { Enumeration e = transactions.keys(); while (e.hasMoreElements()) { Integer id = (Integer) e .nextElement(); if (id != null) { SDPClientTransaction tr = (SDPClientTransaction)transactions.get(id); if (tr != null) { tr.cancel(reason); } } } transactions.clear(); } } /* * Class constructor. * * @param pduID protocol data unit ID * @param ssTransactionID transaction ID of the first request * @param listener listener object which will receive * completion and error notifications */ public SDPClientTransaction(JavaSDPClient client, int pduID, int ssTransactionID, SDPResponseListener listener) { this.pduID = (byte)pduID; this.ssTransID = ssTransactionID; pduTransID = newTransactionID(); this.listener = listener; this.client = client; if (DEBUG) { System.out.println(" Transaction[" + getID() + "] created"); } } /* * Updates the effective transaction ID with a new value. */ private void updatePduTransactionID() { synchronized (transactions) { transactions.remove(new Integer(pduTransID)); pduTransID = newTransactionID(); transactions.put(new Integer(pduTransID), this); } } public String getID() { return ("" + ssTransID + "_" + pduID); } /* * Starts this transaction. * * @throws IOException when an I/O error occurs */ public void run() { if (client == null) { return; } synchronized (transactions) { transactions.put( new Integer( pduTransID ), this); } continuationState = null; SDPClientReceiver.start(client); try { submitRequest(); if (DEBUG) { System.out.println( "Transaction[" + getID() + "]: request sent. Waiting completion" ); } synchronized (this) { wait(); } } catch (InterruptedException ie) { ie.printStackTrace(); } catch (IOException ioe) { ioe.printStackTrace(); } finally { finish(); if (DEBUG) { System.out.println( "Transaction[" + getID() + "] ended" ); } } } public void start() { (new Thread(this)).start(); } /* * Terminates this transaction and reports error to the listener. * * @param reason error code which will be reported */ public void cancel(int reason) { listener.errorResponse(reason, "", ssTransID); finish(); if (DEBUG) { System.out.println( "Transaction[" + getID() + "]: canceled" ); } } /* * Ends this transaction by unregistering it in the outer class. */ public void finish() { synchronized (transactions) { transactions.remove(new Integer(pduTransID)); } if (client != null) { client.removeTransaction(getID()); } SDPClientReceiver.stop(client); synchronized (this) { notify(); } } /* * Reads error PDU, ends this transaction and reports listener the * error code retrieved. * * @param length length of PDU's parameters */ public void error(int length) throws IOException { if (DEBUG) { System.out.println( "Transaction[" + getID() + "] notify error" ); } short errorCode = client.getConnection().getReaderWriter().readShort(); byte[] infoBytes = client.getConnection().getReaderWriter().readBytes(length - 2); listener.errorResponse(errorCode, new String(infoBytes), ssTransID); finish(); } /* * Completes the transaction by calling corresponding listener's * method with the data retrieved. */ abstract void complete(); /* * Writes transaction-specific parameters into the PDU. * * @throws IOException when an I/O error occurs */ abstract void writeParameters() throws IOException; /* * Reads transaction-specific parameters from the PDU. * * @param length length of PDU's parameters * @throws IOException when an I/O error occurs */ abstract void readParameters(int length) throws IOException; /* * Gets next SDP server response, if any, and passes it to the * corresponding listener. If a response is received, the transaction * it belongs to is stopped. * * @throws IOException if an I/O error occurs */ boolean processResponse(byte pduID, short length ) throws IOException { boolean completed = false; try { synchronized (client.getConnection().readLock) { if (DEBUG) { System.out.println("Transaction[" + getID() + "] pdu_id: " + pduID + " response " + length + " bytes received. processing..."); } if (pduID == SDPClientTransaction.SDP_ERROR_RESPONSE) { if (DEBUG) { System.out.println("Transaction[" + getID() + "]: Error response received"); } error(length); return true; } if (DEBUG) { System.out.println("Transaction[" + getID() + "] read parameters..."); } readParameters(length); if (DEBUG) { System.out.println("Transaction[" + getID() + "] read continuation state..."); } completed = readContinuationState(); if (DEBUG) { System.out.println("Transaction[" + getID() + "] request should" + (completed ? "n't" : "") + " be resubmitted"); } } continueResponseProcessing(); } finally { if (completed) { synchronized (this) { notify(); } if (DEBUG) { System.out.println("Transaction[" + getID() + "] finished"); } } } return completed; } /* * Processes this transaction by either re-submitting the original * request if the last response was incomplete, or providing the * listener with the results if the transaction was completed. * * @throws IOException when an I/O error occurs */ private void continueResponseProcessing() throws IOException { if (continuationState != null) { try { if (DEBUG) { System.out.println("Transaction[" + getID() + "] resubmitt request"); } resubmitRequest(); } catch (IOException e) { if (DEBUG) { System.out.println("Transaction[" + getID() + "] error: " + e); } cancel(SDPResponseListener.IO_ERROR); throw e; } } else { if (DEBUG) { System.out.println( "Transaction[" + getID() + "] notify completed" ); } complete(); } } /* * Re-submits the original request with continuation state * data received with the incomplete response. * * @throws IOException when an I/O error occurs */ private void submitRequest() throws IOException { synchronized (client.getConnection().writeLock) { client.getConnection().getReaderWriter().writeByte(pduID); client.getConnection().getReaderWriter().writeShort((short)pduTransID); client.getConnection().getReaderWriter().writeShort((short)(parameterLength + 1)); writeParameters(); if (continuationState != null) { client.getConnection().getReaderWriter().writeByte((byte)continuationState.length); client.getConnection().getReaderWriter().writeBytes(continuationState); } else { client.getConnection().getReaderWriter().writeByte((byte)0x00); } client.getConnection().getReaderWriter().flush(); if (DEBUG) { System.out.println("Transaction[" + getID() + "] request " + parameterLength + 1 + " bytes sent"); } } } private void resubmitRequest() throws IOException { updatePduTransactionID(); submitRequest(); } /* * Extracts continuation state parameter. * * @return true if the continuation state is present, * false otherwise * @throws IOException when an I/O error occurs */ private boolean readContinuationState() throws IOException { byte infoLength = client.getConnection().getReaderWriter().readByte(); if (infoLength == 0) { continuationState = null; } else { continuationState = client.getConnection().getReaderWriter().readBytes(infoLength); } return (infoLength == 0); } }