// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/link/Link.java,v $
// $RCSfile: Link.java,v $
// $Revision: 1.5 $
// $Date: 2004/10/14 18:05:55 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.link;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import com.bbn.openmap.Layer;
import com.bbn.openmap.omGraphics.grid.OMGridGenerator;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.util.Debug;
/**
* The Link object is the main mechanism for communications between a
* LinkClient (most likely a LinkLayer) and a LinkServer. This class
* should only be used directly by a server - clients should use the
* ClientLink object. This object defines the communications that
* either side can make.
* <P>
*
* The ClientLink adds some control methods that the client should
* use. The client needs to make sure that several queries are not
* sent to the server at the same time. The ClientLink contains a lock
* that can be checked, and set. It is up to the client to manage the
* lock. The ClientLink also provides the method to close the link
* down, since it makes that decision. The server should remain
* connected until the client is finished. The server can request to
* be disconnected, however, and the ClientLink provides a method for
* the client to check if that request has been made.
*/
public class Link implements LinkConstants {
/** The apparent maximum size of a header. */
public final static int MAX_HEADER_LENGTH = 80;
/** For outgoing traffic. */
protected LinkOutputStream dos = null;
/** For incoming traffic. */
protected DataInputStream dis = null;
/** Used to read/create strings from off the input stream. */
protected char[] charArray = new char[MAX_HEADER_LENGTH];
/**
* Set for the client, by the server, to indicate whether the
* socket should be closed. By default, this will be false, Used
* when the server wants to run in a stateless mode, and doesn't
* care to maintain a connection with the client. It's included in
* the Link object because the server knows about it and sets it
* in the client.
*/
protected boolean closeLink = false;
/**
* Used to retrieve any potential graphics queries that came in
* over the link.
*/
protected LinkMapRequest mapRequest = null;
/**
* Used to retrieve any potential graphics responses that came in
* over the link.
*/
protected LinkGraphicList graphicList = null;
/**
* Used to retrieve any potential gesture queries that came in
* over the link.
*/
protected LinkActionRequest actionRequest = null;
/**
* Used to retrieve any potential gesture responses that came in
* over the link.
*/
protected LinkActionList actionList = null;
/**
* Used to retrieve any potential GUI queries that came in over
* the link.
*/
protected LinkGUIRequest guiRequest = null;
/**
* Used to retrieve any potential GUI responses that came in over
* the link.
*/
protected LinkGUIList guiList = null;
/** The socket used for the link. Kept for convenience. */
protected Socket socket = null;
/**
* The lock. This should only be changed within a synchronized
* block of code, synchronized on the link object.!! Otherwise,
* race conditions can result.
*/
protected boolean locked = false;
/**
* Flag to control whether this side of the link will adhere to
* shutdown commands issued from other side of the link. False by
* default.
*/
protected boolean obeyCommandToExit = false;
/**
* Open up a link over a socket.
*
* @param socket the socket to open the Link on.
* @throws IOException
*/
public Link(Socket socket) throws IOException {
this.socket = socket;
InputStream is = socket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
this.dis = new DataInputStream(bis);
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
this.dos = new LinkOutputStream(bos);
}
/**
* Should be called by the server and/or client to release
* resources when the link is through being used.
*/
public void cleanUp() {
try {
this.dis.close();
this.dos.close();
} catch (IOException ioe) {
}
this.dis = null;
this.dos = null;
}
/**
* The method to call at the beginning of a request or response.
* It writes the header given to the link. This header is expected
* on the other side of the link.
*
* @param messageHeader Header string, defined in the Link object,
* that describes the tranmission.
* @throws IOException
*/
public void start(String messageHeader) throws IOException {
dos.write(messageHeader.getBytes());
}
/**
* The method that needs to be called at the end of a
* request/response or section. This places the END_TOTAL symbol
* on the link to let the other side know that the transmission is
* done, and it also flushes the output stream buffer.
*
* @param endType use END_SECTION if you want to add more types of
* responses. Use END_TOTAL at the end of the total
* transmission.
* @throws IOException
*/
public void end(String endType) throws IOException {
dos.write(endType.getBytes());
if (END_TOTAL.equals(endType)) {
dos.flush();
}
}
/**
* Called to begin reading the information coming off the link.
* Since the information can be coming in different sections, this
* method figures out how to read the different sections and get
* ready for requests on what was read. After the link is read,
* you can then request the link to find out what was sent back -
* for graphics, GUI components, or actions. When this method is
* called, the link resets the objects that are returned by
* getGraphics(), getGUI and getActions(). These methods are meant
* to be used after read() to find out what was returned.
*
* @throws IOException
*/
public void readAndParse() throws IOException {
readAndParse(null, null);
}
/**
* Called to begin reading the information coming off the link.
*
* @param proj a projection for graphics
* @param generator an OMGridGenerator that knows how to render
* grid objects.
* @throws IOException
*/
public void readAndParse(Projection proj, OMGridGenerator generator)
throws IOException {
readAndParse(proj, generator, null);
}
/**
* Called to begin reading the information coming off the link.
*
* @param proj pass in a projection if you are expecting graphics
* to arrive, and they will be projected as they come off
* the link.
* @param generator an OMGridGenerator that knows how to render
* grid objects.
* @param layer a layer that is interested in gesture reactions.
* @throws IOException
*/
public void readAndParse(Projection proj, OMGridGenerator generator,
Layer layer) throws IOException {
// Reset everything //
// Keep this here, so if there is more than one graphics
// section, then all the graphics get added to one list.
LinkOMGraphicList graphics = new LinkOMGraphicList();
graphicList = null;
mapRequest = null;
actionRequest = null;
actionList = null;
guiRequest = null;
guiList = null;
closeLink = false;
String delimiter = null;
if (Debug.debugging("link")) {
System.out.println("Link|readAndParse: listening to link:");
System.out.println((proj == null ? " without " : " with ")
+ "a projection and");
System.out.println((layer == null ? " without " : " with ")
+ "a layer");
}
while (true) {
delimiter = readDelimiter(true);
if (Debug.debugging("link")) {
System.out.println("Link:reading section: " + delimiter);
}
if (delimiter == GRAPHICS_HEADER) {
if (layer != null) {
graphicList = new LinkGraphicList(this, graphics, layer.getProjection(), generator);
} else {
graphicList = new LinkGraphicList(this, graphics, proj, generator);
}
delimiter = graphicList.getLinkStatus();
} else if (delimiter == ACTIONS_HEADER) {
actionList = new LinkActionList(this, layer, proj, generator);
delimiter = actionList.getLinkStatus();
} else if (delimiter == GUI_HEADER) {
guiList = new LinkGUIList(this);
delimiter = guiList.getLinkStatus();
} else if (delimiter == CLOSE_LINK_HEADER) {
closeLink = true;
} else if (delimiter == SHUTDOWN_HEADER) {
Debug.message("link", "Link.received command to exit");
if (obeyCommandToExit) {
System.exit(0);
}
} else if (delimiter == HUH_HEADER) {
delimiter = readDelimiter(true);
} else if (delimiter == MAP_REQUEST_HEADER) {
mapRequest = new LinkMapRequest(this);
delimiter = mapRequest.getLinkStatus();
} else if (delimiter == ACTION_REQUEST_HEADER) {
actionRequest = new LinkActionRequest(this);
delimiter = actionRequest.getLinkStatus();
} else if (delimiter == GUI_REQUEST_HEADER) {
guiRequest = new LinkGUIRequest(this);
delimiter = guiRequest.getLinkStatus();
} else if (delimiter == PING_REQUEST_HEADER) {
start(PING_RESPONSE_HEADER);
end(END_TOTAL);
delimiter = readDelimiter(false);
}
if (delimiter == END_TOTAL) {
return;
}
}
}
public void setObeyCommandToExit(boolean value) {
obeyCommandToExit = value;
}
public boolean getAcceptCommandToExit() {
return obeyCommandToExit;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve a graphics request, if one was sent.
*
* @return LinkMapRequest containing the request.
*/
public LinkMapRequest getMapRequest() {
return mapRequest;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve graphics in an LinkOMGraphicList, if any
* graphics were sent.
*
* @return GraphicLinkRsponse containing the information. If no
* graphics were sent the list will be empty.
*/
public LinkGraphicList getGraphicList() {
return graphicList;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve a gesture notification/request, if one was
* sent.
*
* @return LinkActionRequest containing the request.
*/
public LinkActionRequest getActionRequest() {
return actionRequest;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve the gesture response.
*
* @return LinkActionList containing the information.
*/
public LinkActionList getActionList() {
return actionList;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve a gesture notification/request, if one was
* sent.
*
* @return LinkGUIRequest containing the request.
*/
public LinkGUIRequest getGUIRequest() {
return guiRequest;
}
/**
* After a readAndParse() has been called on a link, this can be
* called to retrieve the GUI response, if any GUI components were
* sent.
*
*/
public LinkGUIList getGUIList() {
return guiList;
}
/**
* readDelimiter is a function designed to read a header string
* off the data input stream in the Link object. It expects that
* the next byte off the link will be a '<' in the stream, and
* then reads through the stream until it finds the '>' expected
* at the end of the string. It will also return a string version
* of END_TOTAL or END_SECTION if it is encountered instead. If
* desired, an intern version of the string is returned.
*
* @param returnString if true, an intern String version of the
* characters is returned.
* @throws IOException
* @throws ArrayIndexOutOfBoundsException
*/
protected String readDelimiter(boolean returnString) throws IOException,
ArrayIndexOutOfBoundsException {
String ret = END_TOTAL;
char END_TOTAL_CHAR = END_TOTAL.charAt(0);
char END_SECTION_CHAR = END_SECTION.charAt(0);
char c = (char) dis.readByte();
// NOTE: possibility of early exits here...
if (c == END_TOTAL_CHAR) {
Debug.message("link", "Link|readDelimiter: Found END_TOTAL");
return END_TOTAL;
} else if (c == END_SECTION_CHAR) {
Debug.message("link", "Link|readDelimiter: Found END_SECTION");
return END_SECTION;
} else if (c != '<') {
if (Debug.debugging("link")) {
System.out.println("Link|readDelimiter: unexpected protocol data read '"
+ c + "'");
}
throw new IOException("readDelimiter: unexpected protocol data read.");
}
// The byte read does indeed equal '<'
int charCount = 0;
// c should == '<'
charArray[charCount++] = c;
// Get the rest of the header information
c = (char) dis.readByte();
while (c != '>' && charCount < MAX_HEADER_LENGTH - 1) {
charArray[charCount++] = c;
c = (char) dis.readByte();
}
// c should == '>' or uh-oh - too many characters between
// them. Exit with a faulty return if this is the case.
if (c != '>') {
throw new IOException("readDelimiter: header is too long.");
}
charArray[charCount++] = c;
// OK, got it - return string
if (returnString) {
ret = new String(charArray, 0, charCount).intern();
} else {
ret = "";
}
return ret;
}
/**
* Other threads can check to see if the link is in use.
*
* @return true if link in use and unavailable.
*/
public boolean isLocked() {
return locked;
}
/**
* Set the lock. Should only be called in a synchronized block of
* code, where you have control over the link.
*
* @param set true if the lock should be turned on, false if the
* link should be released.
*/
public synchronized boolean setLocked(boolean set) {
if (set == true) {
if (locked == true) {
// The lock was NOT set for the caller - unsuccessful.
return false;
} else {
locked = true;
// The lock was set for the caller, successfully.
return true;
}
} else {
locked = set;
// The state was set to false, successfully.
return true;
}
}
/**
* This method is provided for those who want to optimize how they
* write the graphical objects to the output stream. Look in the
* LinkGraphics API to find out the order of the pieces for
* each graphic type. Not recommended for the faint of heart.
*/
public DataOutput getDOS() {
return dos;
}
/**
* This method complements getDOS().
*/
public DataInput getDIS() {
return dis;
}
/**
* Returns the number of bytes written since the last
* clearBytesWritten() call.
*/
public int getBytesWritten() {
return dos.size();
}
/**
* Reset the bytes written count to 0.
*
* @return the old byte count
*/
public int clearBytesWritten() {
return dos.clearWritten();
}
}