/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services 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.
*
* Granite Data Services 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.client.android.messaging.transport;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.message.BasicHeader;
import org.granite.client.messaging.channel.Channel;
import org.granite.client.messaging.transport.*;
import org.granite.logging.Logger;
import org.granite.util.PublicByteArrayOutputStream;
import android.content.Context;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.BinaryHttpResponseHandler;
import com.loopj.android.http.PersistentCookieStore;
/**
* @author Franck WOLFF
*/
public class LoopjTransport extends AbstractTransport<Context> {
private static final Logger log = Logger.getLogger(LoopjTransport.class);
private AsyncHttpClient httpClient = null;
private PersistentCookieStore cookieStore = null;
public LoopjTransport() {
}
public LoopjTransport(Context context) {
super(context);
}
public boolean isReconnectAfterReceive() {
return true;
}
@Override
public synchronized boolean start() {
if (httpClient != null)
return true;
log.debug("Starting loopj transport...");
Context context = getContext();
if (context == null)
throw new IllegalStateException("Android application context is null");
httpClient = new AsyncHttpClient();
httpClient.setTimeout(60000); // Change default to allow long-polling tunnel
cookieStore = new PersistentCookieStore(context);
httpClient.setCookieStore(cookieStore);
log.debug("Loopj transport started");
return true;
}
@Override
public synchronized boolean isStarted() {
return httpClient != null;
}
protected synchronized AsyncHttpClient getAsyncHttpClient() {
return httpClient;
}
@Override
public TransportFuture send(final Channel channel, final TransportMessage message) throws TransportException {
AsyncHttpClient httpClient = getAsyncHttpClient();
if (httpClient == null) {
TransportException e = new TransportStateException("Apache HttpAsyncClient not started");
getStatusHandler().handleException(e);
throw e;
}
Context context = getContext();
if (context == null) {
TransportException e = new TransportStateException("Android context is null");
getStatusHandler().handleException(e);
throw e;
}
PublicByteArrayOutputStream os = new PublicByteArrayOutputStream(512);
try {
message.encode(os);
}
catch (IOException e) {
throw new TransportException("Message serialization failed: " + message.getId(), e);
}
HttpEntity entity = new ByteArrayEntity(Arrays.copyOfRange(os.getBytes(), 0, os.size()));
Header[] headers = new Header[] {
new BasicHeader("GDSClientType", message.getClientType().toString())
};
log.trace("Posting request to %s", channel.getUri());
httpClient.post(context, channel.getUri().toString(), headers, entity, message.getContentType(), new BinaryHttpResponseHandler(new String[]{message.getContentType()}) {
private boolean handled = false;
@Override
public void onStart() {
if (!message.isConnect())
getStatusHandler().handleIO(true);
}
@Override
public void onSuccess(int statusCode, byte[] bytes) {
handled = true;
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
if (statusCode != HttpStatus.SC_OK) {
channel.onError(message, new TransportHttpStatusException(statusCode, "HTTP Error: " + statusCode));
return;
}
try {
channel.onMessage(message, new ByteArrayInputStream(bytes));
}
catch (Exception e) {
getStatusHandler().handleException(new TransportIOException(message, "Could not deserialize message", e));
}
}
@Override
public void onFailure(Throwable throwable, String s) {
handled = true;
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
Exception e = new TransportException(throwable);
channel.onError(message, new TransportException(e));
getStatusHandler().handleException(new TransportIOException(message, "Request failed", e));
}
@Override
public void onFinish() {
if (!handled) {
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
channel.onMessage(message, new ByteArrayInputStream(new byte[0]));
}
if (!message.isConnect())
getStatusHandler().handleIO(false);
}
});
return new TransportFuture() {
@Override
public boolean cancel() {
throw new UnsupportedOperationException();
}
};
}
@Override
public synchronized void stop() {
if (httpClient != null) {
log.debug("Stopping loopj transport...");
try {
httpClient.cancelRequests(getContext(), true);
}
finally {
httpClient = null;
}
log.debug("Loopj transport stopped");
}
}
}