package org.seqcode.data.readdb;
import java.net.*;
import java.util.*;
import java.io.*;
import java.nio.channels.*;
import javax.security.sasl.*;
import javax.security.auth.callback.*;
/**
* <p>API for remote access to the readdb server.
*
* <p>Calls throw IOException on network errors.
* Calls throw ClientException on other errors (authentication, authorization, invalid request ,etc.
*
* <p>Client generally assumes that the hit positions are the 5' end of the hit.
*
* <p>Client IS NOT REENTRANT. Do not overlap calls to a single Client object.
*
* <p>The current version of Client keeps a separate thread that would close the connection if it is idle too long.
*
* <p>Most method parameters that are object types (eg Integer, Boolean) are optional. If a null value
* is passed then no filtering is done based on that parameter.
* <p>Standard parameters shared across methods:
* <ul>
* <li> alignid is the name of the alignment.
* <li> isType2 specifies whether to work on ordinary single-ended reads (false) or the "type 2" reads (true).
* An example of type 2 reads are the R2 reads in paired-end ChIP-exo.
* <li> isPaired specifies whether to work on single-ended reads (false) or paired-end reads (true)
* <li> isLeft if isPaired is true, then isLeft specifies whether to work on the left read (true)
* or right read (false) of the pair
* <li> plusStrand specifies whether to return only reads on the plus strand (true) or minus strand (false). null
* means that reads on both strands should be returned.
* <li> minWeight specifies the minimum weight of reads (or read pairs) to be returned or included in
* the histogram
* <li> start, stop specify the lowest (inclusive) and highest (exclusive) coordinates of reads
* that should be included in the results
* </ul>
*
*
*/
public class Client implements ReadOnlyClient {
public static final String SaslMechanisms[] = {"CRAM-MD5","DIGEST-MD5"};
Socket socket; //the socket to talk to the server. outstream and instream are from the socket
OutputStream outstream;
BufferedInputStream instream;
Thread closeTimerThread=null; //checks time of last activity - closes connection if idle for too long
byte[] buffer; //temporary space for receiving data; contents not persistent between method calls
private static final int BUFFERLEN = 8192*20;
private final int socketLoadDataReadTimeout = 1000*60*8; //socket timeout in ms: set to 8 minutes because we should only be relying on the timeout to detect server shutdowns, and some uses of Client (e.g. loading a lot of reads to ReadDB) can take a long time on the Server.
private final int socketQueryReadTimeout = 60000; //socket timeout in ms for queries
private final int threadSleepTime = 30000; //check time of last activity thread sleep time in ms
private final int connectionIdleTimeLimit = 1000*60*10; //close idle connection after this time (10 minutes)
private long lastActivityTime; //Time of last activity is updated by each query
private boolean connectionOpen=false;
private Request request;
private boolean printErrors;
private String hostname, username, password;
private int portnum;
/** Connects to a ReadDB server on the specified host and port using the specified
* username and password.
* @throws IOException on network errors
* @throws ClientException if the client cannot authenticate to the server
*/
public Client (String hostname,
int portnum,
String username,
String passwd) throws IOException, ClientException {
init(hostname,portnum,username,passwd);
}
/**
* Creates the default connection
* as specified by ~/.readdb_passwd or a readdb_passwd found in the classpath
* Must have keys hostname, port, username, and passwd in a format
* that java.util.PropertyResourceBundle can read
*
* @throws IOException on network errors
* @throws ClientException if the client cannot authenticate to the server
*/
public Client() throws IOException, ClientException {
String homedir = System.getenv("HOME");
String basename = "readdb_passwd";
if (System.getenv("READDBROLE") != null) {
basename = System.getenv("READDBROLE") + basename;
}
String fname = homedir + "/." + basename;
File propfile = new File(fname);
PropertyResourceBundle bundle = null;
if (propfile.exists() && propfile.canRead()) {
bundle = new PropertyResourceBundle(new FileInputStream(propfile));
if (System.getenv("DEBUGPW") != null) {
System.err.println("Opening readdb properties from " + propfile);
}
} else {
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL url = cl.getResource(basename);
if (System.getenv("DEBUGPW") != null) {
System.err.println("Opening readdb properties from " + url);
}
if (url != null) {
bundle = new PropertyResourceBundle(url.openStream());
} else {
throw new IOException("Can't read connection properties from " + url);
}
}
String hostname = bundle.getString("hostname");
String port = bundle.getString("port");
String username = bundle.getString("username");
String password = bundle.getString("passwd");
init(hostname, Integer.parseInt(port), username, password);
}
private void init(String hostname,
int portnum,
String username,
String passwd) throws IOException, ClientException {
this.hostname=hostname;
this.portnum=portnum;
this.username=username;
this.password = passwd;
if(closeTimerThread!=null && closeTimerThread.isAlive())
closeTimerThread.interrupt();
synchronized(this){
socket = new Socket(hostname,portnum);
socket.setTcpNoDelay(true);
socket.setSendBufferSize(BUFFERLEN);
socket.setReceiveBufferSize(BUFFERLEN);
/* linger = true, time = 0 means that the other side gets a reset if the
socket is closed on our end (eg, java exits). We turn this off just before
sending a "bye" to allow for a graceful shutdown. But the RST in
other cases lets the server figure out that we've disappeared
*/
socket.setSoLinger(true,0);
socket.setSoTimeout(socketQueryReadTimeout);
outstream = socket.getOutputStream();
outstream.flush();
instream = new BufferedInputStream(socket.getInputStream());
connectionOpen=true;
lastActivityTime = System.currentTimeMillis();
buffer = new byte[BUFFERLEN];
if (!authenticate(hostname,username,passwd)) {
throw new ClientException("Authentication Exception Failed");
}
request = new Request();
printErrors = false;
//Start a new check alive thread
closeTimerThread = new Thread(new ClientConnectionTimerThread(this));
closeTimerThread.start();
}
}
/**
* Re-connect to the ReadDB sever using the same settings
*/
public void reConnect(){
try {
init(hostname, portnum, username, password);
} catch (IOException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
}
/**
* Pings the ReadDB server,
* @return true if the server pongs
*/
public boolean connectionAlive(){
String response="";
try{
synchronized (this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="ping";
sendString(request.toString());
response = readLine();
}
if (response.equals("pong")) {
return true;
} else {
return false;
}
}catch(IOException e){
//SocketException could be generated by a timeout
return false;
}
}
/**
* Return some basic information about the server
* @return string
*/
public String getServerInfo(){
return("ReadDB\t"+hostname+"\t"+portnum+"\t"+username);
}
/**
* Determines whether the client will print error messages to STDERR. Useful for debugging
* but may produce unwanted screen output.
*/
public void printErrors(boolean b) {printErrors = b;}
/**
* performs the SASL authentication exchange with the server. currently called by the constructor
*/
private boolean authenticate(String hostname, String username, String password) throws IOException {
SaslClient sasl = null;
try {
sendString(username + "\n");
Map<String,String> props = new HashMap<String,String>();
props.put("Sasl.POLICY_NOPLAINTEXT","true");
props.put("Sasl.POLICY_NOANONYMOUS","true");
sasl = Sasl.createSaslClient(SaslMechanisms,
username,
"readdb",
hostname,
props,
new ClientCallbackHandler(username,password));
if (sasl == null) { return false; }
byte[] response = (sasl.hasInitialResponse() ? sasl.evaluateChallenge(new byte[0]) : new byte[0]);
byte continueByte = 1;
while (continueByte != 0) {
outstream.write((response.length + "\n").getBytes());
outstream.write(response);
outstream.flush();
int length = Integer.parseInt(readLine());
byte[] challenge = new byte[length];
int read = 0;
while (read < length) {
read += instream.read(challenge, read, length - read);
}
/* the continueByte tells us whether the server
expects to do another round. Necessary because sometimes
isComplete() returned true here but the server wasn't done
*/
continueByte = (byte)instream.read();
if (!sasl.isComplete()) {
response = sasl.evaluateChallenge(challenge);
} else {
response = new byte[0];
}
}
sasl.dispose();
String status = readLine();
return (status.equals("authenticated as " + username));
} catch (SaslException e) {
e.printStackTrace();
if (sasl != null) {
sasl.dispose();
}
return false;
}
}
/** sends a string to the server and flushes the socket
*/
private void sendString(String s) throws IOException {
outstream.write(s.getBytes());
outstream.flush();
lastActivityTime = System.currentTimeMillis();
}
/** reads one line from the server. blocking.
*/
private String readLine() throws IOException {
int pos = 0;
int i;
while ((i = instream.read()) != -1) {
if (i == '\n') {
break;
} else {
buffer[pos++] = (byte)i;
}
}
String out = new String(buffer,0,pos);
//System.err.println("READ " + out);
lastActivityTime = System.currentTimeMillis();
return out;
}
/**
* Tells the server to shut itself down. Use this to stop the server process.
* @throws IOException on network errors
* @throws ClientException if the user isn't authorized to shut the server down.
*/
public void shutdown() throws IOException, ClientException{
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type = "shutdown";
sendString(request.toString());
}
}
/** this was to fix a bug in the server. You shouldn't need it for general use.
* Regenerate the index for this alignment and chromosome
*/
public void reIndex(String align, int chrom, boolean isType2, boolean paired) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type = "reindex";
request.alignid = align;
request.chromid = chrom;
request.isType2 = isType2;
request.isPaired = paired;
sendString(request.toString());
outstream.flush();
String response = readLine();
if (!response.equals("OK")) {
System.out.println(response);
}
}
}
/** This was to fix a bug in the server. You shouldn't need it for general use.
* Resort the hits for a single-ended alignment and regenerate the index.
*/
public void checksort(String align, int chrom) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type = "checksort";
request.alignid = align;
request.chromid = chrom;
sendString(request.toString());
outstream.flush();
String response = readLine();
if (!response.equals("OK")) {
System.out.println(response);
}
}
}
/**
* Stores a set of SingleHit objects (representing an un-paired or single-ended read
* aligned to a genome) in the specified alignment. The hits are appended
* to any hits that have already been stored in the alignment.
*/
public void storeSingle(String alignid, List<SingleHit> allhits, boolean isType2) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
socket.setSoTimeout(socketLoadDataReadTimeout);
}
int step = 10000000;
for (int pos = 0; pos < allhits.size(); pos += step) {
Map<Integer, List<SingleHit>> map = new HashMap<Integer,List<SingleHit>>();
for (int i = pos; i < pos + step && i < allhits.size(); i++) {
SingleHit h = allhits.get(i);
if (!map.containsKey(h.chrom)) {
map.put(h.chrom, new ArrayList<SingleHit>());
}
map.get(h.chrom).add(h);
}
for (int chromid : map.keySet()) {
synchronized(this){
List<SingleHit> hits = map.get(chromid);
Collections.sort(hits);
int chunk = step;
for (int startindex = 0; startindex < hits.size(); startindex += chunk) {
int count = ((startindex + chunk) < hits.size()) ? chunk : (hits.size() - startindex);
request.clear();
request.type="storesingle";
request.alignid=alignid;
request.chromid = chromid;
request.isType2 = isType2;
request.map.put("numhits",Integer.toString(count));
try{
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
throw new ClientException(response);
}
int[] ints = new int[count];
for (int i = startindex; i < startindex + count; i++) {
ints[i - startindex] = hits.get(i).pos;
}
Bits.sendInts(ints, outstream,buffer);
float[] floats = new float[count];
for (int i = startindex; i < startindex + count; i++) {
floats[i - startindex] = hits.get(i).weight;
ints[i - startindex] = Hits.makeLAS(hits.get(i).length, hits.get(i).strand);
}
Bits.sendFloats(floats, outstream,buffer);
Bits.sendInts(ints, outstream,buffer);
System.err.println("Sent " + count + " hits to the server for " + chromid + "," + alignid);
outstream.flush();
response = readLine();
if (!response.equals("OK")) {
throw new ClientException(response);
}
}catch (IOException ioe){
//IOException here is probably a socket time-out.
//I think it's best to kill the process at this point, since we won't know if the sent reads actually got loaded.
System.err.println(ioe);
System.exit(1);
}
}
}
}
}
socket.setSoTimeout(socketQueryReadTimeout);
}
/**
* Stores a set of PairedHit objects (representing an paired-ended read
* aligned to a genome) in the specified alignment. The hits are appended
* to any hits that have already been stored in the alignment
*/
public void storePaired(String alignid, List<PairedHit> allhits) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
socket.setSoTimeout(socketLoadDataReadTimeout);
}
Map<Integer, List<PairedHit>> map = new HashMap<Integer,List<PairedHit>>();
for (PairedHit h : allhits) {
if (!map.containsKey(h.leftChrom)) {
map.put(h.leftChrom, new ArrayList<PairedHit>());
}
map.get(h.leftChrom).add(h);
}
for (int chromid : map.keySet()) {
synchronized(this){
List<PairedHit> hits = map.get(chromid);
System.err.println("SENDING PAIRED HITS n="+hits.size() + " for chrom " + chromid);
int chunk = 1000000;
for (int startindex = 0; startindex < hits.size(); startindex += chunk) {
int count = ((startindex + chunk) < hits.size()) ? chunk : (hits.size() - startindex);
request.clear();
request.type="storepaired";
request.alignid=alignid;
request.chromid=chromid;
request.isLeft=true;
request.map.put("numhits",Integer.toString(count));
try{
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
throw new ClientException(response);
}
int[] ints = new int[count];
for (int i = startindex; i < startindex + count; i++) {
ints[i-startindex] = hits.get(i).leftPos;
}
Bits.sendInts(ints, outstream,buffer);
float[] floats = new float[count];
int[] codes = new int[count];
for (int i = startindex; i < startindex + count; i++) {
floats[i-startindex] = hits.get(i).weight;
codes[i-startindex] = hits.get(i).pairCode;
ints[i-startindex] = Hits.makeLAS(hits.get(i).leftLength, hits.get(i).leftStrand,
hits.get(i).rightLength, hits.get(i).rightStrand);
}
Bits.sendFloats(floats, outstream,buffer);
Bits.sendInts(codes, outstream,buffer);
Bits.sendInts(ints, outstream,buffer);
for (int i = startindex; i < startindex + count; i++) {
ints[i-startindex] = hits.get(i).rightChrom;
}
Bits.sendInts(ints, outstream,buffer);
for (int i = startindex; i < startindex + count; i++) {
ints[i-startindex] = hits.get(i).rightPos;
}
Bits.sendInts(ints, outstream,buffer);
System.err.println("Sent " + count + " hits to the server");
outstream.flush();
response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
}catch (IOException ioe){
//IOException here is probably a socket time-out.
//I think it's best to kill the process at this point, since we won't know if the sent reads actually got loaded.
System.err.println(ioe);
System.exit(1);
}
}
}
}
socket.setSoTimeout(socketQueryReadTimeout);
}
/** Returns true if the alignment and chromosome exist and are accessible
* to the user. Returns false if they don't exist or if they aren't accessible
*/
public boolean exists(String alignid) throws IOException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="exists";
request.alignid=alignid;
sendString(request.toString());
String response = readLine();
if (response.equals("exists")) {
return true;
} else if (response.equals("unknown")) {
return false;
} else {
return false;
}
}
}
/**
* Deletes an alignment (all chromosomes). isPaired specifies whether to delete
* the paired or single ended reads.
*/
public void deleteAlignment(String alignid, boolean isPaired) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="deletealign";
request.isPaired = isPaired;
request.alignid=alignid;
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
}
}
/**
* Returns the set of chromosomes that exist for this alignment.
*/
public Set<Integer> getChroms(String alignid, boolean isType2, boolean isPaired, Boolean isLeft) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="getchroms";
request.isType2 = isType2;
request.isLeft = isLeft;
request.isPaired = isPaired;
request.alignid=alignid;
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numchroms = Integer.parseInt(readLine());
Set<Integer> output = new HashSet<Integer>();
while (numchroms-- > 0) {
output.add(Integer.parseInt(readLine()));
}
return output;
}
}
/**
* Returns the total number of hits in this alignment.
*/
public int getCount(String alignid, boolean isType2, boolean isPaired, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
int count = 0;
for (int c : getChroms(alignid, isType2, isPaired, isLeft)) {
count += getCount(alignid, c, isType2, isPaired, null,null,null,isLeft,plusStrand);
}
return count;
}
/**
* Returns the sum of the weights of all hits in this alignment
*/
public double getWeight(String alignid, boolean isType2, boolean isPaired, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
double total = 0;
for (int c : getChroms(alignid, isType2, isPaired, isLeft)) {
total += getWeight(alignid, c, isType2, isPaired, null, null, null, isLeft, plusStrand);
}
return total;
}
/** returns the total number of hits on the specified chromosome in the alignment.
* Any of the object parameters can be set to null to specify "no value"
*/
public int getCount(String alignid, int chromid, boolean isType2, boolean paired, Integer start, Integer stop, Float minWeight, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="count";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = paired;
request.isLeft = isLeft == null ? true : isLeft;
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numhits = Integer.parseInt(readLine());
return numhits;
}
}
/** returns the total weight on the specified chromosome in this alignment
*/
public double getWeight(String alignid, int chromid, boolean isType2, boolean paired, Integer start, Integer stop, Float minWeight, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="weight";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = paired;
request.isLeft = isLeft == null ? true : isLeft;
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
return Double.parseDouble(readLine());
}
}
/**
* returns the sorted (ascending order) hit positions in the specified range of a chromosome,alignment pair.
*/
public int[] getPositions(String alignid, int chromid, boolean isType2, boolean paired, Integer start, Integer stop, Float minWeight, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="gethits";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = paired;
request.isLeft = isLeft;
request.map.put("wantpositions","1");
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numhits = Integer.parseInt(readLine());
return Bits.readInts(numhits, instream, buffer);
}
}
/**
* returns the hit weights in the specified range of a chromosome,alignment pair. The weights
* will be in the same order as the sorted positions returned by getPositions()
*/
public float[] getWeightsRange(String alignid, int chromid, boolean isType2, boolean paired, Integer start, Integer stop, Float minWeight, Boolean isLeft, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="gethits";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = paired;
request.isLeft = isLeft;
request.map.put("wantweights","1");
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numhits = Integer.parseInt(readLine());
return Bits.readFloats(numhits, instream, buffer);
}
}
public List<SingleHit> getSingleHits(String alignid, int chromid, boolean isType2, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="gethits";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = false;
request.map.put("wantpositions","1");
request.map.put("wantweights","1");
request.map.put("wantlengthsandstrands","1");
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
List<SingleHit> output = new ArrayList<SingleHit>();
int numhits = Integer.parseInt(readLine());
for (int i = 0; i < numhits; i++) {
output.add(new SingleHit(chromid,0,(float)0.0,false,(short)0));
}
IntBP ints = new IntBP(numhits);
ReadableByteChannel rbc = Channels.newChannel(instream);
Bits.readBytes(ints.bb, rbc);
for (int i = 0; i < numhits; i++) {
output.get(i).pos = ints.get(i);
}
FloatBP floats = new FloatBP(numhits);
Bits.readBytes(floats.bb, rbc);
for (int i = 0; i < numhits; i++) {
output.get(i).weight = floats.get(i);
}
Bits.readBytes(ints.bb, rbc);
for (int i = 0; i < numhits; i++) {
int j = ints.get(i);
SingleHit h = output.get(i);
h.length = Hits.getLengthOne(j);
h.strand = Hits.getStrandOne(j);
}
return output;
}
}
public List<PairedHit> getPairedHits(String alignid, int chromid, boolean isLeft, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="gethits";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isPlusStrand = plusStrand;
request.isLeft = isLeft;
request.isType2 = false;
request.isPaired = true;
request.map.put("wantpositions","1");
request.map.put("wantweights","1");
request.map.put("wantpaircodes","1");
request.map.put("wantlengthsandstrands","1");
request.map.put("wantotherchroms","1");
request.map.put("wantotherpositions","1");
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(String.format("align %s chrom %d: %s", alignid, chromid, response));
}
List<PairedHit> output = new ArrayList<PairedHit>();
int numhits = Integer.parseInt(readLine());
for (int i = 0; i < numhits; i++) {
output.add(new PairedHit(chromid,0,false,(short)0,
chromid,0,false,(short)0,(float)0,0));
}
IntBP ints = new IntBP(numhits);
ReadableByteChannel rbc = Channels.newChannel(instream);
Bits.readBytes(ints.bb, rbc);
if (isLeft) {
for (int i = 0; i < numhits; i++) {
output.get(i).leftPos = ints.get(i);
}
} else {
for (int i = 0; i < numhits; i++) {
output.get(i).rightPos = ints.get(i);
}
}
FloatBP floats = new FloatBP(numhits);
Bits.readBytes(floats.bb, rbc);
for (int i = 0; i < numhits; i++) {
output.get(i).weight = floats.get(i);
}
Bits.readBytes(ints.bb, rbc);
for (int i = 0; i < numhits; i++) {
output.get(i).pairCode = ints.get(i);
}
Bits.readBytes(ints.bb, rbc);
if (isLeft) {
for (int i = 0; i < numhits; i++) {
int j = ints.get(i);
PairedHit h = output.get(i);
h.leftLength = Hits.getLengthOne(j);
h.leftStrand = Hits.getStrandOne(j);
h.rightLength = Hits.getLengthTwo(j);
h.rightStrand = Hits.getStrandTwo(j);
}
} else {
for (int i = 0; i < numhits; i++) {
int j = ints.get(i);
PairedHit h = output.get(i);
h.leftLength = Hits.getLengthTwo(j);
h.leftStrand = Hits.getStrandTwo(j);
h.rightLength = Hits.getLengthOne(j);
h.rightStrand = Hits.getStrandOne(j);
}
}
Bits.readBytes(ints.bb, rbc);
if (isLeft) {
for (int i = 0; i < numhits; i++) {
output.get(i).rightChrom = ints.get(i);
}
} else {
for (int i = 0; i < numhits; i++) {
output.get(i).leftChrom = ints.get(i);
}
}
Bits.readBytes(ints.bb, rbc);
if (isLeft) {
for (int i = 0; i < numhits; i++) {
output.get(i).rightPos = ints.get(i);
}
} else {
for (int i = 0; i < numhits; i++) {
output.get(i).leftPos = ints.get(i);
}
}
return output;
}
}
/**
* returns a TreeMap from positions to counts representing a histogram
* of the hits in a range with the specified binsize. Bins with a count
* of zero are not included in the output.
*
* Ex: getHistgram("foo","1+",1,100,10)
* 6: 5
* 16: 0
* 36: 30
*
* minweight is the minimum weight for reads to be included in the histogram.
*
* dedup is the limit on how many times reads with any given 5' position will be counted.
* A value of zero means no limit. A limit of, eg, 2, means that at most two reads at
* any 5' position will be included in the output. For weighted histograms, the choice of reads
* included is unspecified. For methods that operate on a set of alignments, this many
* reads from each alignment will be included.
*
* Normally, a read is only counted in a single bin as defined by its position (generally
* the 5' end of the read). A non-zero read-Extension counts the read in any
* bin that you cross within that many bases of the read's position. A negative value
* goes backwards (smaller coordinates) and a larger value goes forward. You need
* to get the sign right depending on the strandedness of the chromosome that you're
* working with.
*/
public TreeMap<Integer,Integer> getHistogram(String alignid, int chromid, boolean isType2, boolean paired, int extension, int binsize, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
return getHistogram(alignid, chromid, isType2, paired, extension,binsize,0,start,stop,minWeight,plusStrand,true);
}
public TreeMap<Integer,Integer> getHistogram(String alignid, int chromid, boolean isType2, boolean paired, int extension, int binsize, int dedup, Integer start, Integer stop, Float minWeight, Boolean plusStrand, boolean isLeft) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="histogram";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.isLeft = isLeft;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isPaired = paired;
request.map.put("binsize",Integer.toString(binsize));
if (dedup > 0) {
request.map.put("dedup",Integer.toString(dedup));
}
if (extension != 0) {
request.map.put("extension",Integer.toString(extension));
}
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numints = Integer.parseInt(readLine());
int out[] = Bits.readInts(numints, instream, buffer);
TreeMap<Integer,Integer> output = new TreeMap<Integer,Integer>();
for (int i = 0; i < out.length; i += 2) {
output.put(out[i], out[i+1]);
}
return output;
}
}
public TreeMap<Integer,Float> getWeightHistogram(String alignid, int chromid, boolean isType2, boolean paired, int extension, int binsize, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
return getWeightHistogram(alignid, chromid, isType2, paired, extension, binsize, 0, start,stop,minWeight,plusStrand, true);
}
public TreeMap<Integer,Float> getWeightHistogram(String alignid, int chromid, boolean isType2, boolean paired, int extension, int binsize, int dedup, Integer start, Integer stop, Float minWeight, Boolean plusStrand, boolean isLeft) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="weighthistogram";
request.alignid=alignid;
request.chromid=chromid;
request.start = start;
request.end = stop;
request.minWeight = minWeight;
request.isType2 = isType2;
request.isPlusStrand = plusStrand;
request.isLeft = isLeft;
request.isPaired = paired;
request.map.put("binsize",Integer.toString(binsize));
if (dedup > 0)
request.map.put("dedup",Integer.toString(dedup));
if (extension!=0)
request.map.put("extension",Integer.toString(extension));
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
int numints = Integer.parseInt(readLine());
int out[] = Bits.readInts(numints, instream, buffer);
float weight[] = Bits.readFloats(numints, instream,buffer);
TreeMap<Integer,Float> output = new TreeMap<Integer,Float>();
for (int i = 0; i < out.length; i++) {
output.put(out[i], weight[i]);
}
return output;
}
}
public TreeMap<Integer,Integer> getHistogram(Collection<String> alignids, int chromid, boolean isType2, boolean paired, int extension, int binsize, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
return getHistogram(alignids,chromid,isType2, paired,extension,binsize,0,start,stop,minWeight,plusStrand);
}
public TreeMap<Integer,Integer> getHistogram(Collection<String> alignids, int chromid, boolean isType2, boolean paired, int extension, int binsize, int dedup, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
TreeMap<Integer,Integer> output = null;
for (String alignid : alignids) {
TreeMap<Integer,Integer> o = getHistogram(alignid,chromid,isType2, paired,extension,binsize,dedup,start,stop,minWeight,plusStrand,true);
if(paired) //run for isLeft =true & false
o.putAll(getHistogram(alignid,chromid,isType2, paired,extension,binsize,dedup,start,stop,minWeight,plusStrand,false));
for (int k : o.keySet()) {
if ((k - start - binsize / 2) % binsize != 0 ) {
System.err.println(String.format("Bad key %d for binsize %d and start %d in %s,%d",
k, binsize, start, alignid,chromid));
}
}
if (output == null) {
output = o;
} else {
for (int k : o.keySet()) {
if (output.containsKey(k)) {
output.put(k, output.get(k) + o.get(k));
} else {
output.put(k,o.get(k));
}
}
}
}
return output;
}
public TreeMap<Integer,Float> getWeightHistogram(Collection<String> alignids, int chromid, boolean isType2, boolean paired, int extension, int binsize, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
return getWeightHistogram(alignids,chromid,isType2, paired,extension,binsize,0,start,stop,minWeight,plusStrand);
}
public TreeMap<Integer,Float> getWeightHistogram(Collection<String> alignids, int chromid, boolean isType2, boolean paired, int extension, int binsize, int dedup, Integer start, Integer stop, Float minWeight, Boolean plusStrand) throws IOException, ClientException {
TreeMap<Integer,Float> output = null;
for (String alignid : alignids) {
TreeMap<Integer,Float> o = getWeightHistogram(alignid,chromid,isType2, paired,extension,binsize,dedup,start,stop,minWeight,plusStrand, true);
if(paired) //run for isLeft =true & false
o.putAll(getWeightHistogram(alignid,chromid,isType2, paired,extension,binsize,dedup,start,stop,minWeight,plusStrand, false));
if (output == null) {
output = o;
} else {
for (int k : o.keySet()) {
if (output.containsKey(k)) {
output.put(k, output.get(k) + o.get(k));
} else {
output.put(k,o.get(k));
}
}
}
}
return output;
}
/**
* Returns a Map from READ, WRITE, and ADMIN to lists of principals that have those privileges on the specified alignment.
*/
public Map<String,Set<String>> getACL(String alignid) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="getacl";
request.alignid=alignid;
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
Map<String,Set<String>> output = new HashMap<String,Set<String>>();
fillPartACL(output);
fillPartACL(output);
fillPartACL(output);
return output;
}
}
/* fills one section of the acl output data structure. A section
is either read, write, or admin.
*/
private void fillPartACL(Map<String,Set<String>> output) throws IOException {
synchronized(this){
if(!connectionOpen)
reConnect();
String type = readLine();
int entries = Integer.parseInt(readLine());
Set<String> out = new HashSet<String>();
while (entries-- > 0) {
out.add(readLine());
}
output.put(type,out);
}
}
/**
* Applies the specified ACLChangeEntry objects to the acl for this experiment/chromosome.
*/
public void setACL(String alignid, Set<ACLChangeEntry> changes) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="setacl";
request.alignid=alignid;
for (ACLChangeEntry a : changes) {
request.list.add(a.toString());
}
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
}
}
/**
* Adds the specified user (princ) to a group.
*/
public void addToGroup(String princ, String group) throws IOException, ClientException {
synchronized(this){
if(!connectionOpen)
reConnect();
request.clear();
request.type="addtogroup";
request.map.put("princ",princ);
request.map.put("group",group);
sendString(request.toString());
String response = readLine();
if (!response.equals("OK")) {
if (printErrors) {
System.err.println("not-OK response to request: " + response);
System.err.println("request was " + request);
}
throw new ClientException(response);
}
}
}
/**
* Closes this connection to the server.
*/
public void close() {
if(closeTimerThread!=null && closeTimerThread.isAlive())
closeTimerThread.interrupt();
if(connectionOpen)
closeConnection();
connectionOpen=false;
}
/**
* Closes this connection to the server.
*/
public void closeConnection() {
if (socket == null) {
return;
}
try {
socket.setSoLinger(false,0);
request.clear();
request.type="bye";
sendString(request.toString());
outstream.close();
outstream = null;
} catch (IOException e) {
e.printStackTrace();
}
try {
instream.close();
instream = null;
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
connectionOpen=false;
//System.err.println("Readdb client closed");
}
/**
* ClientConnectionTimerThread closes the connection if it is idle for too long
* @author mahony
*
*/
class ClientConnectionTimerThread implements Runnable{
Client parent; //reference to parent class
public ClientConnectionTimerThread(Client p){
parent = p;
}
public void run() {
while(true){
try {
Thread.sleep(threadSleepTime);
//System.out.println("ClientConnectionTimerThread: checking ("+(System.currentTimeMillis() - lastActivityTime)+")\tconnectionOpen="+connectionOpen);
if(connectionOpen && System.currentTimeMillis() - lastActivityTime >connectionIdleTimeLimit){
parent.closeConnection();
//System.out.println("ClientConnectionTimerThread: connection closed");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
}
/**
* SASL callback handler for the authentication exchange
*/
class ClientCallbackHandler implements CallbackHandler {
private String name, pass;
public ClientCallbackHandler(String n, String p) {name = n; pass = p;}
public void handle(Callback[] callbacks) {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback nc = (NameCallback)callbacks[i];
nc.setName(name);
}
if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback)callbacks[i];
pc.setPassword(pass.toCharArray());
}
}
}
}