/**
* 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.messaging.transport.apache;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Future;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.granite.client.messaging.channel.Channel;
import org.granite.client.messaging.transport.AbstractTransport;
import org.granite.client.messaging.transport.TransportException;
import org.granite.client.messaging.transport.TransportFuture;
import org.granite.client.messaging.transport.TransportHttpStatusException;
import org.granite.client.messaging.transport.TransportIOException;
import org.granite.client.messaging.transport.TransportMessage;
import org.granite.client.messaging.transport.TransportStateException;
import org.granite.logging.Logger;
import org.granite.util.PublicByteArrayOutputStream;
/**
* @author Franck WOLFF
*/
public class ApacheAsyncTransport extends AbstractTransport<Object> {
private static final Logger log = Logger.getLogger(ApacheAsyncTransport.class);
private CloseableHttpAsyncClient httpClient = null;
private RequestConfig defaultRequestConfig = null;
private BasicCookieStore cookieStore = new BasicCookieStore();
public ApacheAsyncTransport() {
}
public void configure(HttpAsyncClientBuilder clientBuilder) {
// Can be overwritten...
}
public RequestConfig getDefaultRequestConfig() {
return defaultRequestConfig;
}
public void setDefaultRequestConfig(RequestConfig defaultRequestConfig) {
this.defaultRequestConfig = defaultRequestConfig;
}
protected synchronized CloseableHttpAsyncClient getCloseableHttpAsyncClient() {
return httpClient;
}
public boolean isReconnectAfterReceive() {
return true;
}
public boolean isDisconnectAfterAuthenticationFailure() {
return false;
}
@Override
public synchronized boolean start() {
if (httpClient != null)
return true;
log.info("Starting Apache HttpAsyncClient transport...");
try {
if (defaultRequestConfig == null)
defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY).build();
HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom();
httpClientBuilder.setDefaultCookieStore(cookieStore);
httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
configure(httpClientBuilder);
httpClient = httpClientBuilder.build();
httpClient.start();
log.info("Apache HttpAsyncClient transport started.");
return true;
}
catch (Exception e) {
httpClient = null;
getStatusHandler().handleException(new TransportException("Could not start Apache HttpAsyncClient", e));
log.error(e, "Apache HttpAsyncClient failed to start.");
return false;
}
}
@Override
public synchronized boolean isStarted() {
return httpClient != null;
}
@Override
public TransportFuture send(final Channel channel, final TransportMessage message) throws TransportException {
CloseableHttpAsyncClient httpClient = getCloseableHttpAsyncClient();
if (httpClient == null) {
TransportException e = new TransportStateException("Apache HttpAsyncClient not started");
getStatusHandler().handleException(e);
throw e;
}
if (!message.isConnect())
getStatusHandler().handleIO(true);
try {
HttpPost request = new HttpPost(channel.getUri());
request.setHeader("Content-Type", message.getContentType());
request.setHeader("GDSClientType", message.getClientType().toString());
PublicByteArrayOutputStream os = new PublicByteArrayOutputStream(512);
try {
message.encode(os);
}
catch (IOException e) {
throw new TransportException("Message serialization failed: " + message.getId(), e);
}
request.setEntity(new ByteArrayEntity(os.getBytes(), 0, os.size()));
final Future<HttpResponse> future = httpClient.execute(request, new FutureCallback<HttpResponse>() {
public void completed(HttpResponse response) {
if (!message.isConnect())
getStatusHandler().handleIO(false);
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
channel.onError(message, new TransportHttpStatusException(
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase())
);
return;
}
InputStream is = null;
try {
is = response.getEntity().getContent();
channel.onMessage(message, is);
}
catch (Exception e) {
getStatusHandler().handleException(new TransportIOException(message, "Could not deserialize message", e));
}
finally {
if (is != null) try {
is.close();
}
catch (Exception e) {
}
}
}
public void failed(Exception e) {
if (!message.isConnect())
getStatusHandler().handleIO(false);
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
channel.onError(message, e);
getStatusHandler().handleException(new TransportIOException(message, "Request failed", e));
}
public void cancelled() {
if (!message.isConnect())
getStatusHandler().handleIO(false);
if (message.isDisconnect()) {
channel.onDisconnect();
return;
}
channel.onCancelled(message);
}
});
return new TransportFuture() {
@Override
public boolean cancel() {
boolean cancelled = false;
try {
cancelled = future.cancel(true);
}
catch (Exception e) {
log.error(e, "Cancel request failed");
}
return cancelled;
}
};
}
catch (Exception e) {
if (!message.isConnect())
getStatusHandler().handleIO(false);
TransportIOException f = new TransportIOException(message, "Request failed", e);
getStatusHandler().handleException(f);
throw f;
}
}
@Override
public synchronized void stop() {
if (httpClient == null)
return;
log.info("Stopping Apache HttpAsyncClient transport...");
super.stop();
try {
httpClient.close();
}
catch (Exception e) {
getStatusHandler().handleException(new TransportException("Could not stop Apache HttpAsyncClient", e));
log.error(e, "Apache HttpAsyncClient failed to stop properly.");
}
finally {
httpClient = null;
}
log.info("Apache HttpAsyncClient transport stopped.");
}
}