001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.jdbc.rest;
017
018import java.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.net.HttpURLConnection;
022import java.net.MalformedURLException;
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.net.URL;
026import org.apache.http.HttpEntity;
027import org.apache.http.HttpHost;
028import org.apache.http.HttpResponse;
029import org.apache.http.auth.AuthScope;
030import org.apache.http.auth.UsernamePasswordCredentials;
031import org.apache.http.client.HttpClient;
032import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
033import org.apache.http.client.methods.HttpGet;
034import org.apache.http.client.methods.HttpPost;
035import org.apache.http.client.methods.HttpRequestBase;
036import org.apache.http.client.protocol.HttpClientContext;
037import org.apache.http.client.utils.URIBuilder;
038import org.apache.http.entity.BufferedHttpEntity;
039import org.apache.http.entity.InputStreamEntity;
040import org.apache.http.impl.client.BasicCredentialsProvider;
041import org.apache.http.impl.client.HttpClientBuilder;
042import org.apache.http.util.EntityUtils;
043import org.codehaus.jettison.json.JSONException;
044import org.codehaus.jettison.json.JSONObject;
045import org.modeshape.common.text.UrlEncoder;
046import org.modeshape.common.util.CheckArg;
047import org.modeshape.common.util.StringUtil;
048
049/**
050 * A simple client which can be used to make REST calls to a server.
051 *
052 * @author Horia Chiorean (hchiorea@redhat.com)
053 */
054@SuppressWarnings( "deprecation" )
055public final class JSONRestClient {
056
057    private static final UrlEncoder URL_ENCODER = new UrlEncoder().setSlashEncoded(false);
058    protected final HttpClient httpClient;
059    private final HttpClientContext httpContext;
060    private final HttpHost host;
061    private final String url;
062    private final String baseUrl;
063
064    protected JSONRestClient( String url,
065                              String username,
066                              String password ) {
067        CheckArg.isNotNull(url, "url");
068        try {
069            this.url = url;
070            URL connectionUrl = new URL(url);
071            this.host = new HttpHost(connectionUrl.getHost(), connectionUrl.getPort(), connectionUrl.getProtocol());
072            String urlPath = connectionUrl.getPath();
073            if (urlPath.length() > 0) {
074                String[] segments = urlPath.split("\\/");
075                this.baseUrl = this.host.toURI() + "/" + (segments[0].length() > 0 ? segments[0] : segments[1]);
076            } else {
077                this.baseUrl = this.host.toURI();
078            }
079            this.httpClient = HttpClientBuilder.create().build();
080            this.httpContext = HttpClientContext.create();
081            if (!StringUtil.isBlank(username)) {
082                BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
083                credentialsProvider.setCredentials(new AuthScope(host()),
084                                                   new UsernamePasswordCredentials(username, password));
085                httpContext.setCredentialsProvider(credentialsProvider);
086            }
087        } catch (MalformedURLException e) {
088            throw new IllegalArgumentException("Invalid URL string: " + url, e);
089        }
090    }
091
092    protected String url() {
093        return url;
094    }
095
096    protected HttpHost host() {
097        return host;
098    }
099
100    protected Response doGet() {
101        return new Response(newJSONRequest(HttpGet.class, null, null, null));
102    }
103
104    protected Response doGet( String url ) {
105        return new Response(newJSONRequest(HttpGet.class, null, null, url));
106    }
107
108    protected Response postStream( InputStream is,
109                                   String url,
110                                   String requestContentType ) {
111        HttpPost post = newJSONRequest(HttpPost.class, is, requestContentType, url);
112        return new Response(post);
113    }
114
115    protected Response postStreamTextPlain( InputStream is,
116                                            String url,
117                                            String requestContentType ) {
118        HttpPost post = newRequest(HttpPost.class, is, requestContentType, MediaType.TEXT_PLAIN, url);
119        return new Response(post);
120    }
121
122    private <T extends HttpRequestBase> T newJSONRequest( Class<T> clazz,
123                                                          InputStream inputStream,
124                                                          String contentType,
125                                                          String url ) {
126        return newRequest(clazz, inputStream, contentType, MediaType.APPLICATION_JSON, url);
127    }
128
129    private <T extends HttpRequestBase> T newRequest( Class<T> clazz,
130                                                      InputStream inputStream,
131                                                      String contentType,
132                                                      String accepts,
133                                                      String url ) {
134        assert accepts != null;
135        try {
136            if (url == null) {
137                url = baseUrl;
138            } else if (!url.startsWith(host.getSchemeName())) {
139                url = appendToBaseURL(url);
140            }
141            URIBuilder uriBuilder;
142            try {
143                uriBuilder = new URIBuilder(url);
144            } catch (URISyntaxException e) {
145                uriBuilder = new URIBuilder(URL_ENCODER.encode(url));
146            }
147
148            T result = clazz.getConstructor(URI.class).newInstance(uriBuilder.build());
149            result.setHeader("Accept", accepts);
150            if (contentType != null) {
151                result.setHeader("Content-Type", contentType);
152            }
153            if (inputStream != null) {
154                assert result instanceof HttpEntityEnclosingRequestBase;
155                InputStreamEntity inputStreamEntity = new InputStreamEntity(inputStream, inputStream.available());
156                ((HttpEntityEnclosingRequestBase)result).setEntity(new BufferedHttpEntity(inputStreamEntity));
157            }
158
159            return result;
160        } catch (Exception e) {
161            throw new RuntimeException(e);
162        }
163    }
164
165    protected static String append( String url,
166                                    String... segments ) {
167        for (String segment : segments) {
168            if (url.endsWith(segment)) {
169                continue;
170            }
171            if (!url.endsWith("/")) {
172                url = url + "/";
173            }
174            if (segment.startsWith("/")) {
175                segment = segment.substring(1);
176            }
177            url += segment;
178        }
179        return url;
180    }
181
182    protected String appendToBaseURL( String... segments ) {
183        return append(baseUrl, segments);
184    }
185
186    protected String appendToURL( String... segments ) {
187        return append(url, segments);
188    }
189
190    protected interface MediaType {
191        String APPLICATION_JSON = "application/json";
192        String TEXT_PLAIN = "text/plain;";
193    }
194
195    protected class Response {
196
197        private final HttpResponse response;
198        private byte[] content;
199        private String contentString;
200        private JSONObject contentJSON;
201
202        protected Response( HttpRequestBase request ) {
203            try {
204                response = httpClient.execute(host(), request, httpContext);
205                HttpEntity entity = response.getEntity();
206                if (entity != null) {
207                    ByteArrayOutputStream baous = new ByteArrayOutputStream();
208                    entity.writeTo(baous);
209                    EntityUtils.consumeQuietly(entity);
210                    content = baous.toByteArray();
211                } else {
212                    content = new byte[0];
213                }
214            } catch (IOException e) {
215                throw new RuntimeException(e);
216            } finally {
217                request.releaseConnection();
218            }
219        }
220
221        public boolean isOK() {
222            return hasCode(HttpURLConnection.HTTP_OK);
223        }
224
225        private int getStatusCode() {
226            return response.getStatusLine().getStatusCode();
227        }
228
229        private boolean hasCode( int statusCode ) {
230            return getStatusCode() == statusCode;
231        }
232
233        public JSONObject json() {
234            try {
235                if (contentJSON == null) {
236                    contentJSON = new JSONObject(asString());
237                }
238                return contentJSON;
239            } catch (JSONException e) {
240                throw new RuntimeException(e);
241            }
242        }
243
244        public String asString() {
245            if (contentString == null) {
246                contentString = new String(content);
247            }
248            return contentString;
249        }
250
251        @Override
252        public String toString() {
253            return asString();
254        }
255    }
256}