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    }