001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 * This file is part of Granite Data Services.
006 *
007 * Granite Data Services is free software; you can redistribute it and/or modify
008 * it under the terms of the GNU Library General Public License as published by
009 * the Free Software Foundation; either version 2 of the License, or (at your
010 * option) any later version.
011 *
012 * Granite Data Services is distributed in the hope that it will be useful, but
013 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015 * for more details.
016 *
017 * You should have received a copy of the GNU Library General Public License
018 * along with this library; if not, see <http://www.gnu.org/licenses/>.
019 */
020 package org.granite.client.messaging.transport.apache;
021
022 import java.io.IOException;
023 import java.io.InputStream;
024 import java.util.concurrent.Future;
025
026 import org.apache.http.HttpResponse;
027 import org.apache.http.HttpStatus;
028 import org.apache.http.client.config.CookieSpecs;
029 import org.apache.http.client.config.RequestConfig;
030 import org.apache.http.client.methods.HttpPost;
031 import org.apache.http.concurrent.FutureCallback;
032 import org.apache.http.entity.ByteArrayEntity;
033 import org.apache.http.impl.client.BasicCookieStore;
034 import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
035 import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
036 import org.apache.http.impl.nio.client.HttpAsyncClients;
037 import org.granite.client.messaging.channel.Channel;
038 import org.granite.client.messaging.transport.AbstractTransport;
039 import org.granite.client.messaging.transport.HTTPTransport;
040 import org.granite.client.messaging.transport.TransportException;
041 import org.granite.client.messaging.transport.TransportFuture;
042 import org.granite.client.messaging.transport.TransportHttpStatusException;
043 import org.granite.client.messaging.transport.TransportIOException;
044 import org.granite.client.messaging.transport.TransportMessage;
045 import org.granite.client.messaging.transport.TransportStateException;
046 import org.granite.logging.Logger;
047 import org.granite.util.PublicByteArrayOutputStream;
048
049 /**
050 * @author Franck WOLFF
051 */
052 public class ApacheAsyncTransport extends AbstractTransport<Object> implements HTTPTransport {
053
054 private static final Logger log = Logger.getLogger(ApacheAsyncTransport.class);
055
056 private CloseableHttpAsyncClient httpClient = null;
057 private RequestConfig defaultRequestConfig = null;
058
059 public ApacheAsyncTransport() {
060 }
061
062 public void configure(HttpAsyncClientBuilder clientBuilder) {
063 // Can be overwritten...
064 }
065
066 public RequestConfig getDefaultRequestConfig() {
067 return defaultRequestConfig;
068 }
069
070 public void setDefaultRequestConfig(RequestConfig defaultRequestConfig) {
071 this.defaultRequestConfig = defaultRequestConfig;
072 }
073
074 protected synchronized CloseableHttpAsyncClient getCloseableHttpAsyncClient() {
075 return httpClient;
076 }
077
078 @Override
079 public synchronized boolean start() {
080 if (httpClient != null)
081 return true;
082
083 log.info("Starting Apache HttpAsyncClient transport...");
084
085 try {
086 if (defaultRequestConfig == null)
087 defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.BROWSER_COMPATIBILITY).build();
088
089 HttpAsyncClientBuilder httpClientBuilder = HttpAsyncClients.custom();
090 httpClientBuilder.setDefaultCookieStore(new BasicCookieStore());
091 httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);
092 configure(httpClientBuilder);
093 httpClient = httpClientBuilder.build();
094
095 httpClient.start();
096
097 log.info("Apache HttpAsyncClient transport started.");
098 return true;
099 }
100 catch (Exception e) {
101 httpClient = null;
102 getStatusHandler().handleException(new TransportException("Could not start Apache HttpAsyncClient", e));
103
104 log.error(e, "Apache HttpAsyncClient failed to start.");
105 return false;
106 }
107 }
108
109 @Override
110 public synchronized boolean isStarted() {
111 return httpClient != null;
112 }
113
114 @Override
115 public TransportFuture send(final Channel channel, final TransportMessage message) throws TransportException {
116 CloseableHttpAsyncClient httpClient = getCloseableHttpAsyncClient();
117 if (httpClient == null) {
118 TransportException e = new TransportStateException("Apache HttpAsyncClient not started");
119 getStatusHandler().handleException(e);
120 throw e;
121 }
122
123 if (!message.isConnect())
124 getStatusHandler().handleIO(true);
125
126 try {
127 HttpPost request = new HttpPost(channel.getUri());
128 request.setHeader("Content-Type", message.getContentType());
129 request.setHeader("GDSClientType", message.getClientType().toString());
130
131 PublicByteArrayOutputStream os = new PublicByteArrayOutputStream(512);
132 try {
133 message.encode(os);
134 }
135 catch (IOException e) {
136 throw new TransportException("Message serialization failed: " + message.getId(), e);
137 }
138 request.setEntity(new ByteArrayEntity(os.getBytes(), 0, os.size()));
139
140 final Future<HttpResponse> future = httpClient.execute(request, new FutureCallback<HttpResponse>() {
141
142 public void completed(HttpResponse response) {
143 if (!message.isConnect())
144 getStatusHandler().handleIO(false);
145
146 if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
147 channel.onError(message, new TransportHttpStatusException(
148 response.getStatusLine().getStatusCode(),
149 response.getStatusLine().getReasonPhrase())
150 );
151 return;
152 }
153
154 InputStream is = null;
155 try {
156 is = response.getEntity().getContent();
157 channel.onMessage(is);
158 }
159 catch (Exception e) {
160 getStatusHandler().handleException(new TransportIOException(message, "Could not deserialize message", e));
161 }
162 finally {
163 if (is != null) try {
164 is.close();
165 }
166 catch (Exception e) {
167 }
168 }
169 }
170
171 public void failed(Exception e) {
172 if (!message.isConnect())
173 getStatusHandler().handleIO(false);
174
175 channel.onError(message, e);
176 getStatusHandler().handleException(new TransportIOException(message, "Request failed", e));
177 }
178
179 public void cancelled() {
180 if (!message.isConnect())
181 getStatusHandler().handleIO(false);
182
183 channel.onCancelled(message);
184 }
185 });
186
187 return new TransportFuture() {
188 @Override
189 public boolean cancel() {
190 boolean cancelled = false;
191 try {
192 cancelled = future.cancel(true);
193 }
194 catch (Exception e) {
195 log.error(e, "Cancel request failed");
196 }
197 return cancelled;
198 }
199 };
200 }
201 catch (Exception e) {
202 if (!message.isConnect())
203 getStatusHandler().handleIO(false);
204
205 TransportIOException f = new TransportIOException(message, "Request failed", e);
206 getStatusHandler().handleException(f);
207 throw f;
208 }
209 }
210
211 public synchronized void poll(final Channel channel, final TransportMessage message) throws TransportException {
212 throw new TransportException("Not implemented");
213 }
214
215 @Override
216 public synchronized void stop() {
217 if (httpClient == null)
218 return;
219
220 log.info("Stopping Apache HttpAsyncClient transport...");
221
222 super.stop();
223
224 try {
225 httpClient.close();
226 }
227 catch (Exception e) {
228 getStatusHandler().handleException(new TransportException("Could not stop Apache HttpAsyncClient", e));
229
230 log.error(e, "Apache HttpAsyncClient failed to stop properly.");
231 }
232 finally {
233 httpClient = null;
234 }
235
236 log.info("Apache HttpAsyncClient transport stopped.");
237 }
238 }