/*
* Copyright 2012 Couchbase, Inc.
*
* Licensed 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.couchbase.mock.http;
import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.couchbase.mock.Bucket;
import org.couchbase.mock.harakiri.HarakiriMonitor;
/**
* @author M. Nunberg
*/
class BucketsStreamingHandler implements Observer {
private final Socket rawSocket;
private final OutputStream output;
private final Bucket bucket;
private final HarakiriMonitor monitor;
private final Lock updateHandlerLock = new ReentrantLock();
private final Condition condHasUpdatedConfig = updateHandlerLock.newCondition();
private volatile boolean hasUpdatedConfig = false;
private volatile boolean shouldTerminate = false;
// Some more book keeping stuff for the socket
private final ByteBuffer dummyBuf = ByteBuffer.allocate(1);
private static final byte[] chunkedDelimiter = "\n\n\n\n".getBytes();
public BucketsStreamingHandler(HarakiriMonitor monitor, Bucket bucket, Socket socket) {
this.bucket = bucket;
this.monitor = monitor;
this.rawSocket = socket;
try {
this.output = socket.getOutputStream();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private byte[] getConfigBytes() {
return StateGrabber.getBucketJSON(bucket).getBytes();
}
private void writeChunk(byte[] chunk) throws IOException {
String s = String.format("%x\r\n", chunk.length);
output.write(s.getBytes());
output.write(chunk);
output.write("\r\n".getBytes());
}
private void writeConfigBytes(byte[] payload) throws IOException {
writeChunk(payload);
writeChunk(chunkedDelimiter);
output.flush();
}
@Override
public void update(Observable o, Object arg) {
updateHandlerLock.lock();
try {
hasUpdatedConfig = true;
condHasUpdatedConfig.signalAll();
} finally {
updateHandlerLock.unlock();
}
}
private boolean checkIfClosed() throws IOException {
SocketChannel ch = rawSocket.getChannel();
ch.configureBlocking(false);
dummyBuf.rewind();
try {
int nBytes = ch.read(dummyBuf);
// Anything other than 0 bytes is bad (keep in mind that in this case, 0 means "No data")
return nBytes == 0;
} catch (IOException ex) {
return false;
} finally {
ch.configureBlocking(true);
}
}
private boolean streamNewConfig() throws InterruptedException {
updateHandlerLock.lock();
boolean isLocked = true;
try {
while (!shouldTerminate && !hasUpdatedConfig) {
condHasUpdatedConfig.await(10, TimeUnit.MILLISECONDS);
// See if the underlying connection has been closed yet?
try {
if (!checkIfClosed()) {
return false;
}
} catch (ClosedChannelException ex) {
return false;
} catch (IOException ex) {
ex.printStackTrace();
return false;
}
}
isLocked = false;
updateHandlerLock.unlock();
if (hasUpdatedConfig) {
writeConfigBytes(getConfigBytes());
hasUpdatedConfig = false;
return true;
} else {
return false;
}
} catch (IOException e) {
shouldTerminate = false;
return false;
} finally {
if (isLocked) {
updateHandlerLock.unlock();
}
}
}
public void startStreaming() throws IOException, InterruptedException {
byte[] configBytes;
bucket.configReadLock();
configBytes = getConfigBytes();
// Lock the updater lock, make sure updates are not received
// before we send our initial (older) data.
updateHandlerLock.lock();
if (monitor != null) {
monitor.addObserver(this);
}
// This can be unlocked, because we have wired the update() method.
// Therefore, any changes will be placed AFTER our 'initial' frozen
// snapshot.
bucket.configReadUnlock();
try {
writeConfigBytes(configBytes);
} finally {
// Allow any subsequent updates to be sent
updateHandlerLock.unlock();
}
while (streamNewConfig()) {
//
}
/*
* Error or some such
*/
output.close();
}
}