/*
* Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.MaxReadSizeExceededException;
/**
* data source network based base implementation
*
* @author grro@xlightweb.org
*/
abstract class AbstractNetworkBodyDataSource extends NonBlockingBodyDataSource {
private static final Logger LOG = Logger.getLogger(AbstractNetworkBodyDataSource.class.getName());
private final AbstractHttpConnection httpConnection;
private final AtomicBoolean isSuspendedRef = new AtomicBoolean(false);
private final AtomicBoolean isConnectedRef = new AtomicBoolean(true);
private final HttpHeader header;
private static final boolean DEFAULT_IS_AUTODETECTEDING_ENCODING = Boolean.parseBoolean(System.getProperty("org.xlightweb.autodetectedingEncoding", "true"));
private boolean isDetectEncoding = !DEFAULT_IS_AUTODETECTEDING_ENCODING;
private final AtomicReference<Runnable> autoEncodingCallbackRef = new AtomicReference<Runnable>(null);
private byte[] encodingBuffer = null;
public AbstractNetworkBodyDataSource(HttpHeader header, AbstractHttpConnection httpConnection) {
super(header.getCharacterEncoding(), httpConnection.getExecutor());
this.header = header;
this.httpConnection = httpConnection;
if ((header.getContentType()) != null && HttpUtils.isTextMimeType(header.getContentType()) && HttpUtils.parseEncoding(header.getContentType()) == null) {
isDetectEncoding = true;
} else {
isDetectEncoding = false;
}
}
private void setDetectEncoding(boolean isDetectEncoding) {
synchronized (autoEncodingCallbackRef) {
this.isDetectEncoding = isDetectEncoding;
if (isDetectEncoding == false) {
Runnable task = autoEncodingCallbackRef.getAndSet(null);
if (task != null) {
task.run();
}
}
}
}
final void registerAutoEncondingDetectCallback(Runnable task) {
synchronized (autoEncodingCallbackRef) {
if (!isDetectEncoding) {
task.run();
} else {
autoEncodingCallbackRef.set(task);
}
}
}
protected final String getId() {
return httpConnection.getId();
}
protected final void setNonPersistent() {
httpConnection.setPersistent(false);
}
/**
* {@inheritDoc}
*/
@Override
final void suspend() throws IOException {
// has not already suspended?
if (!isSuspendedRef.getAndSet(true)) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("suspend receiving data");
}
Runnable suspendTask = new Runnable() {
public void run() {
try {
httpConnection.suspendReceiving();
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by suspending " + ioe.toString());
}
}
}
};
httpConnection.getExecutor().processNonthreaded(suspendTask);
}
}
/**
* {@inheritDoc}
*/
@Override
boolean isSuspended() {
return isSuspendedRef.get();
}
/**
* {@inheritDoc}
*/
@Override
void resume() throws IOException {
// is suspended?
if (isSuspendedRef.getAndSet(false)) {
Runnable resumeTask = new Runnable() {
public void run() {
try {
if (httpConnection.isReceivingSuspended()) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] resume receiving data");
}
httpConnection.resumeReceiving();
callBodyDataHandler(true);
}
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] error occured by resuming " + ioe.toString());
}
}
}
};
httpConnection.getExecutor().processNonthreaded(resumeTask);
}
}
final void onDisconnect() throws IOException {
setDetectEncoding(false);
if (isConnectedRef.getAndSet(false)) {
try {
performOnDisconnect();
} catch (ProtocolException pe) {
setException(pe);
throw pe;
}
}
}
abstract void performOnDisconnect() throws IOException;
@Override
final void onDestroy(String reason) {
setDetectEncoding(false);
httpConnection.destroy(reason);
}
final void setComplete() throws IOException {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + getId() + "] completed reveived");
}
setDetectEncoding(false);
httpConnection.onMessageBodyReceived(header);
super.setComplete();
}
final int readByteBufferByLength(ByteBuffer[] buffers, int length) throws IOException {
if (buffers == null) {
return 0;
}
if (isDetectEncoding) {
byte[] data = DataConverter.toBytes(HttpUtils.copy(buffers));
if (encodingBuffer != null) {
byte[] newData = new byte[encodingBuffer.length + data.length];
System.arraycopy(encodingBuffer, 0, newData, 0, encodingBuffer.length);
System.arraycopy(data, 0, newData, encodingBuffer.length, data.length);
data = newData;
}
try {
String encoding = HttpUtils.detectEncoding(data);
if (encoding != null) {
setEncoding(encoding);
header.setCharacterEncoding(encoding);
}
encodingBuffer = null;
setDetectEncoding(false);
} catch (BufferUnderflowException bue) {
encodingBuffer = data;
}
}
int remaining = length;
for (int i = 0; i < buffers.length; i++) {
ByteBuffer buffer = buffers[i];
if (buffer == null) {
continue;
}
int available = buffer.remaining();
if (available == 0) {
continue;
}
// not enough data
if (available < remaining) {
append(buffer);
buffers[i] = null;
remaining -= available;
continue;
} else if (available == remaining) {
append(buffer);
buffers[i] = null;
return 0;
// available > remaining
} else {
int limit = buffer.limit();
buffer.limit(buffer.position() + remaining);
ByteBuffer buf = buffer.slice();
append(buf);
buffer.position(buffer.limit());
buffer.limit(limit);
return 0;
}
}
return remaining;
}
/**
* parses the the body
*
* @param rawData the raw read network data buffer
* @return true, if the body is complete
* @throws IOException if an exception occurs
* @throws BufferUnderflowException if more data is required
* @throws MaxReadSizeExceededException if the max read size is exceeded
*/
final void parse(ByteBuffer[] rawData) throws IOException, BufferUnderflowException, MaxReadSizeExceededException {
if (!isComplete()) {
try {
doParse(rawData);
} catch (BufferUnderflowException bue) {
throw bue;
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("[" + httpConnection.getId() + "] (protocol?) error occured by reading body " + ioe.toString());
}
if (!isComplete()) {
setException(ioe);
}
throw ioe;
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("do not parse, because body is already complete");
}
}
}
/**
* parse the body
* @param rawData the raw read network data buffer
* @throws IOException if an exception occurs
* @return true, if body is complete
*/
abstract boolean doParse(ByteBuffer[] rawData) throws IOException;
void onException(IOException ioe, ByteBuffer[] rawData) {
setException(ioe);
}
final void add(ByteBuffer buffer) throws IOException {
append(buffer);
}
}