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