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.commands; 021 022import java.io.IOException; 023import java.util.Map; 024 025import org.apache.http.client.methods.CloseableHttpResponse; 026import org.apache.http.client.methods.HttpPost; 027import org.apache.http.client.methods.HttpPut; 028import org.apache.http.client.methods.HttpRequestBase; 029import org.apache.http.impl.client.CloseableHttpClient; 030import org.apache.http.impl.client.HttpClients; 031import org.apache.http.util.EntityUtils; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import net.sf.jkniv.reflect.beans.ObjectProxy; 036import net.sf.jkniv.reflect.beans.ObjectProxyFactory; 037import net.sf.jkniv.reflect.beans.PropertyAccess; 038import net.sf.jkniv.sqlegance.Insertable; 039import net.sf.jkniv.sqlegance.RepositoryException; 040import net.sf.jkniv.whinstone.Queryable; 041import net.sf.jkniv.whinstone.couchdb.HttpBuilder; 042 043/** 044 * <pre> 045 * 046 * http://docs.couchdb.org/en/2.0.0/api/document/common.html 047 * 048 * PUT /{db}/{docid} 049 * 050 * The PUT method creates a new named document, or creates a new revision of the existing document. 051 * Unlike the POST /{db}, you must specify the document ID in the request URL. 052 * 053 * Parameters: 054 * 055 * db – Database name 056 * docid – Document ID 057 * 058 * Request Headers: 059 * 060 * 061 * Accept – 062 * application/json 063 * text/plain 064 * Content-Type – application/json 065 * If-Match – Document’s revision. Alternative to rev query parameter 066 * X-Couch-Full-Commit – Overrides server’s commit policy. Possible values are: false and true. Optional 067 * 068 * Query Parameters: 069 * 070 * 071 * batch (string) – Stores document in batch mode. Possible values: ok. Optional 072 * new_edits (boolean) – Prevents insertion of a conflicting document. 073 * Possible values: true (default) and false. If false, a well-formed _rev must be included 074 * in the document. new_edits=false is used by the replicator to insert documents into 075 * the target database even if that leads to the creation of conflicts. Optional 076 * 077 * Response Headers: 078 * 079 * 080 * Content-Type – 081 * application/json 082 * text/plain; charset=utf-8 083 * ETag – Quoted document’s new revision 084 * Location – Document URI 085 * 086 * Response JSON Object: 087 * 088 * 089 * id (string) – Document ID 090 * ok (boolean) – Operation status 091 * rev (string) – Revision MVCC token 092 * 093 * Status Codes: 094 * 095 * 201 Created – Document created and stored on disk 096 * 202 Accepted – Document data accepted, but not yet stored on disk 097 * 400 Bad Request – Invalid request body or parameters 098 * 401 Unauthorized – Write privileges required 099 * 404 Not Found – Specified database or document ID doesn’t exists 100 * 409 Conflict – Document with the specified ID already exists or specified revision is not 101 * latest for target document 102 * 103 * </pre> 104 * 105 * @author Alisson Gomes 106 * @since 0.6.0 107 * 108 */ 109public class AddCommand extends AbstractCommand implements CouchCommand 110{ 111 private static final Logger LOG = LoggerFactory.getLogger(AddCommand.class); 112 private static final Logger LOGSQL = net.sf.jkniv.whinstone.couchdb.LoggerFactory.getLogger(); 113 private Queryable queryable; 114 private HttpBuilder httpBuilder; 115 116 public AddCommand(HttpBuilder httpBuilder, Queryable queryable) 117 { 118 super(); 119 this.httpBuilder = httpBuilder; 120 this.queryable = queryable; 121 this.method = HttpMethod.PUT; 122 this.body = JsonMapper.mapper(queryable.getParams()); 123 } 124 125 @SuppressWarnings("unchecked") 126 @Override 127 public <T> T execute() 128 { 129 String json = null; 130 CloseableHttpResponse response = null; 131 //Map<String, Object> answer = null; 132 T answer = null; 133 try 134 { 135 CloseableHttpClient httpclient = HttpClients.createDefault(); 136 String url = httpBuilder.getUrlForAddOrUpdateOrDelete(queryable); 137 HttpRequestBase http = null; 138 if (url.equals(httpBuilder.getHostContext())) 139 { 140 http = asPost().newHttp(url); 141 ((HttpPost) http).setEntity(getEntity()); 142 } 143 else 144 { 145 http = asPut().newHttp(url); 146 ((HttpPut) http).setEntity(getEntity()); 147 } 148 // FIXME supports header request for PUT commands -> Headers: "If-Match", "X-Couch-Full-Commit" 149 httpBuilder.setHeader(http); 150 printRequest(http); 151 response = httpclient.execute(http); 152 json = EntityUtils.toString(response.getEntity()); 153 printResponse(response, json); 154 int statusCode = response.getStatusLine().getStatusCode(); 155 if (isCreated(statusCode)) 156 { 157 processResponse(queryable, json); 158 answer = (T) Integer.valueOf("1"); 159 } 160 else if (isAccepted(statusCode)) 161 { 162 LOG.info("Document data accepted, but not yet stored on disk"); 163 processResponse(queryable, json); 164 answer = (T) Integer.valueOf("1"); 165 } 166 else if (isNotFound(statusCode)) 167 { 168 answer = (T) Integer.valueOf("0"); 169 // 204 No Content, 304 Not Modified, 205 Reset Content, 404 Not Found 170 LOG.warn(errorFormat(http, response.getStatusLine(), json)); 171 } 172 else 173 { 174 Map<String, String> result = JsonMapper.mapper(json, Map.class); 175 String reason = result.get("reason"); 176 LOG.error(errorFormat(http, response.getStatusLine(), json)); 177 throw new RepositoryException(response.getStatusLine().toString() + ", " + reason); 178 } 179 //commandHandler.postCommit(); 180 } 181 catch (Exception e) // ClientProtocolException | JsonParseException | JsonMappingException | IOException 182 { 183 //commandHandler.postException(); 184 handlerException.handle(e); 185 } 186 finally 187 { 188 if (response != null) 189 { 190 try 191 { 192 response.close(); 193 } 194 catch (IOException e) 195 { 196 handlerException.handle(e); 197 } 198 } 199 } 200 return answer; 201 } 202 203 @SuppressWarnings("unchecked") 204 private void processResponse(Queryable queryable, String json) 205 { 206 Map<String, Object> response = JsonMapper.mapper(json, Map.class); 207 Insertable sql = queryable.getDynamicSql().asInsertable(); 208 Object params = queryable.getParams(); 209 ObjectProxy<?> proxy = ObjectProxyFactory.of(params); 210 PropertyAccess accessId = queryable.getDynamicSql().getSqlDialect().getAccessId(); 211 PropertyAccess accessRev = queryable.getDynamicSql().getSqlDialect().getAccessRevision(); 212 String id = (String) response.get(accessId.getFieldName()); 213 String rev = (String) response.get(accessRev.getFieldName()); 214 if (sql.isAutoGenerateKey()) 215 { 216 String properName = sql.getAutoGeneratedKey().getProperties(); 217 injectAutoIdentity(proxy, params, id, rev, properName, accessId, accessRev); 218 } 219 else 220 { 221 injectIdentity(proxy, params, id, rev, accessId, accessRev); 222 } 223 } 224 225 @Override 226 public String getBody() 227 { 228 return this.body; 229 } 230 231 @Override 232 public HttpMethod asPut() 233 { 234 this.method = HttpMethod.PUT; 235 return this.method; 236 } 237 238 @Override 239 public HttpMethod asPost() 240 { 241 this.method = HttpMethod.POST; 242 return this.method; 243 } 244 245}