001/* 002 * JKNIV, whinstone one contract to access your database. 003 * 004 * Copyright (C) 2017, the original author or authors. 005 * 006 * This library is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 2.1 of the License. 010 * 011 * This library is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with this library; if not, write to the Free Software Foundation, Inc., 018 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 019 */ 020package net.sf.jkniv.whinstone.couchdb; 021 022import static net.sf.jkniv.whinstone.couchdb.statement.AllDocsQueryParams.KEY_limit; 023import static net.sf.jkniv.whinstone.couchdb.statement.AllDocsQueryParams.KEY_skip; 024 025import java.nio.charset.Charset; 026import java.util.Arrays; 027import java.util.List; 028 029import org.apache.http.Consts; 030import org.apache.http.client.methods.HttpPost; 031import org.apache.http.client.methods.HttpRequestBase; 032import org.apache.http.entity.StringEntity; 033 034import net.sf.jkniv.reflect.beans.PropertyAccess; 035import net.sf.jkniv.sqlegance.RepositoryException; 036import net.sf.jkniv.whinstone.Param; 037import net.sf.jkniv.whinstone.Queryable; 038import net.sf.jkniv.whinstone.couchdb.statement.QueryParam; 039import net.sf.jkniv.whinstone.params.ParameterNotFoundException; 040 041public class HttpBuilder 042{ 043 private final String url; 044 private final String schema; 045 private final String hostContext; 046 private final Charset charset; 047 private RequestParams requestParams; 048 private CouchDbAuthenticate auth; 049 // TODO refactor this lot of query params 050 private static final List<String> KEY_PARAMS_ALLDOCS = Arrays.asList("conflicts", "descending", "endkey", 051 "end_key", "endkey_docid", "end_key_doc_id", "include_docs", "inclusive_end", "key", "keys", "stale", 052 "startkey", "start_key", "startkey_docid", "start_key_doc_id", "update_seq"); /*"limit","skip"*/ 053 054 private static final List<QueryParam> KEY_PARAMS_VIEW = Arrays.asList( 055 new QueryParam("key", true), // 056 new QueryParam("keys", true), // 057 new QueryParam("startkey", true),// 058 new QueryParam("endkey", true), // 059 new QueryParam("descending"), // 060 new QueryParam("group"), // 061 new QueryParam("group_level"), // 062 new QueryParam("startkey_docid"),// 063 new QueryParam("endkey_docid"), // 064 new QueryParam("limit"), // 065 new QueryParam("skip"), // 066 new QueryParam("descending"), // 067 new QueryParam("stale"), // 068 new QueryParam("reduce"), // 069 new QueryParam("include_docs"), // 070 new QueryParam("inclusive_end"),// 071 new QueryParam("update_seq")); 072 073 private static final List<QueryParam> KEY_PARAMS_GET = Arrays.asList(new QueryParam("attachments"), 074 new QueryParam("att_encoding_info"), new QueryParam("atts_since"), new QueryParam("conflicts"), 075 new QueryParam("deleted_conflicts"), new QueryParam("latest"), new QueryParam("local_seq"), 076 new QueryParam("meta"), new QueryParam("open_revs"), new QueryParam("rev"), new QueryParam("revs"), 077 new QueryParam("revs_info")); 078 079 private static final List<QueryParam> KEY_PARAMS_SAVE = Arrays.asList(new QueryParam("batch"), 080 new QueryParam("new_edits")); 081 082 private static final List<String> KEY_PARAMS_UNSUPPORTED = Arrays.asList("attachments"); 083 084 public HttpBuilder(CouchDbAuthenticate auth, String url, String schema, RequestParams requestParams) 085 { 086 super(); 087 this.auth = auth; 088 this.url = url; 089 this.schema = schema; 090 this.hostContext = getHostContext(); 091 this.requestParams = requestParams; 092 this.charset = Charset.forName("UTF-8"); 093 } 094 095 public void setHeader(HttpRequestBase http) 096 { 097 String token = auth.getCookieSession(); 098 099 if (auth.isExpired()) 100 token = auth.authenticate(); 101 102 http.addHeader("Cookie", token); 103 requestParams.setHeader(http); 104 } 105 106 /** 107 * Return the URL for _design of documents. 108 * <p> 109 * <code> 110 * http://{host}:{port}/{schema}/_design/ 111 * </code> 112 * 113 * @return URL follow the pattern http://{host}:{port}/{schema}/_design/ 114 */ 115 public String getUrlForDesign() 116 { 117 return this.hostContext + "_design/"; 118 } 119 120 /** 121 * Return the HTTP POST for _find request. 122 * <p> 123 * <code> 124 * http://{host}:{port}/{schema}/_find/ 125 * </code> 126 * 127 * @param bodyStr body from HTTP POST request 128 * @return URL follow the pattern http://{host}:{port}/{schema}/_find/ with {@code bodyStr} as body content 129 */ 130 public HttpPost newFind(String bodyStr) 131 { 132 HttpPost httpPost = null; 133 try 134 { 135 StringEntity body = new StringEntity(bodyStr, Consts.UTF_8); // TODO config charset for HTTP body 136 String fullUrl = this.hostContext + "_find"; 137 httpPost = new HttpPost(fullUrl); 138 requestParams.setHeader(httpPost); 139 httpPost.setEntity(body); 140 } 141 catch (Exception ex) 142 { 143 throw new RepositoryException("Cannot build new URI [" + this.hostContext + "_find]"); 144 } 145 return httpPost; 146 } 147 148 /** 149 * Return the HTTP POST for _find request. 150 * <p> 151 * <code> 152 * http://{host}:{port}/{schema}/_find/ 153 * </code> 154 * 155 * @return URL follow the pattern http://{host}:{port}/{schema}/_find/ without body content 156 */ 157 public HttpPost newFind() 158 { 159 HttpPost http = null; 160 try 161 { 162 String fullUrl = this.hostContext + "_find"; 163 http = new HttpPost(fullUrl); 164 requestParams.setHeader(http); 165 } 166 catch (Exception ex) 167 { 168 throw new RepositoryException("Cannot build new URI [" + this.hostContext + "_find]"); 169 } 170 return http; 171 } 172 173 public String getUrlForView(Queryable queryable) 174 { 175 checkUnsupportedQueryParams(queryable); 176 StringBuilder urlParams = new StringBuilder("?"); 177 StringBuilder urlAllDocs = new StringBuilder(this.hostContext + "_design/" + queryable.getName()); 178 try 179 { 180 // https://docs.couchdb.org/en/stable/ddocs/views/pagination.html 181 // 3.2.5.5. Paging (Alternate Method) 182 // curl -X GET 'http://127.0.0.1:5984/artists/_design/artists/_view/by-name?limit=5&skip=5' 183 if (queryable.isPaging()) 184 urlParams.append("limit="+queryable.getMax()+"&skip="+queryable.getOffset()); 185 186 for (QueryParam k : KEY_PARAMS_VIEW) 187 { 188 Param param = getProperty(queryable, k.name()); 189 if (param.getValue() != null) 190 { 191 if (urlParams.length() > 1) 192 urlParams.append("&" + k.name() + "=" + k.getValue(param.getValue())); 193 else 194 urlParams.append(k.name() + "=" + k.getValue(param.getValue())); 195 } 196 } 197 } 198 catch (Exception ex) 199 { 200 throw new RepositoryException("Cannot build new URI [" + this.hostContext + "_view]"); 201 } 202 return urlAllDocs.toString() + urlParams.toString(); 203 } 204 205 public String getUrlForGet(Queryable queryable) 206 { 207 checkUnsupportedQueryParams(queryable); 208 StringBuilder urlParams = new StringBuilder("?"); 209 StringBuilder urlGet = new StringBuilder(this.hostContext); 210 PropertyAccess accessId = queryable.getDynamicSql().getSqlDialect().getAccessId(); 211 if (queryable.isTypeOfBasic()) 212 urlGet.append(queryable.getParams());// FIXME design docid is Date, Calendar, how to parser 213 else 214 { 215 Param param = getProperty(queryable, accessId.getFieldName()); 216 if (param.getValue() != null) 217 urlGet.append(param.getValue()); 218 else 219 throw new RepositoryException("Cannot lookup id using Property Access ["+accessId+"] from [" + queryable + "]"); 220 } 221 222 try 223 { 224 for (QueryParam k : KEY_PARAMS_GET) 225 { 226 Param param = getProperty(queryable, k.name()); 227 if (param.getValue() != null) 228 { 229 if (urlParams.length() > 1) 230 urlParams.append("&" + k.name() + "=" + k.getValue(param.getValue())); 231 else 232 urlParams.append(k.name() + "=" + k.getValue(param.getValue())); 233 } 234 } 235 } 236 catch (Exception ex) 237 { 238 throw new RepositoryException("Cannot build new URI [" + urlGet.toString() + urlParams.toString() + "]"); 239 } 240 return urlGet.toString() + urlParams.toString(); 241 } 242 243 public String getUrlForAddOrUpdateOrDelete(Queryable queryable) 244 { 245 checkUnsupportedQueryParams(queryable); 246 StringBuilder urlSave = new StringBuilder(this.hostContext); 247 PropertyAccess accessId = queryable.getDynamicSql().getSqlDialect().getAccessId(); 248 Param param = getProperty(queryable, accessId.getFieldName()); 249 if (param.getValue() != null) 250 urlSave.append(param.getValue()); 251 252 return urlSave.toString(); 253 } 254 255 public String getUrlForAllDocs(Queryable queryable) 256 { 257 checkUnsupportedQueryParams(queryable); 258 StringBuilder urlParams = new StringBuilder("?"); 259 StringBuilder urlAllDocs = new StringBuilder(this.hostContext + "_all_docs"); 260 try 261 { 262 if (queryable.isPaging()) 263 urlParams.append(KEY_limit + "=" + queryable.getMax() + "&" + KEY_skip + "=" + queryable.getOffset()); 264 265 for (String k : KEY_PARAMS_ALLDOCS) 266 { 267 Param v = getProperty(queryable, k); 268 if (v.getValue() != null) 269 { 270 if (urlParams.length() > 1) 271 urlParams.append("&" + k + "=" + v.getValue()); 272 else 273 urlParams.append(k + "=" + v.getValue()); 274 } 275 } 276 } 277 catch (Exception ex) 278 { 279 throw new RepositoryException("Cannot build new URI [" + this.hostContext + "_all_docs]"); 280 } 281 return urlAllDocs.toString() + urlParams.toString(); 282 } 283 284 public String getUrlForBulk() 285 { 286 StringBuilder urlSave = new StringBuilder(this.hostContext); 287 urlSave.append("_bulk_docs"); 288 return urlSave.toString(); 289 } 290 291 292 public String getHostContext() 293 { 294 String hostContext = this.url + "/" + this.schema; 295 if (this.url.endsWith("/")) 296 hostContext = this.url + this.schema; 297 298 return hostContext + "/"; 299 } 300 301 public Param getProperty(Queryable queryable, String name) 302 { 303 Param v = null; 304 try 305 { 306 v = queryable.getProperty(name); 307 } 308 catch (ParameterNotFoundException ignore) { /* parameter not exixts */} 309 return v == null ? new Param() : v; 310 } 311 312 private void checkUnsupportedQueryParams(Queryable queryable) 313 { 314 for (String k : KEY_PARAMS_UNSUPPORTED) 315 { 316 Param v = getProperty(queryable, k); 317 if (v.getValue() != null) 318 throw new RepositoryException("Query Parameters [" + k + "] isn't supported yet!"); 319 } 320 } 321}