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.HttpDelete; 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 * DELETE /{db}/{docid} 046 * 047 * Marks the specified document as deleted by adding a field _deleted with the value true. Documents with this field will not be returned within requests anymore, but stay in the database. You must supply the current (latest) revision, either by using the rev parameter or by using the If-Match header to specify the revision. 048 * 049 * Note 050 * 051 * CouchDB doesn’t completely delete the specified document. Instead, it leaves a tombstone with very basic information about the document. The tombstone is required so that the delete action can be replicated across databases. 052 * 053 * See also 054 * 055 * Retrieving Deleted Documents 056 * Parameters: 057 * 058 * db – Database name 059 * docid – Document ID 060 * 061 * Request Headers: 062 * 063 * 064 * Accept – 065 * application/json 066 * text/plain 067 * If-Match – Document’s revision. Alternative to rev query parameter 068 * X-Couch-Full-Commit – Overrides server’s commit policy. Possible values are: false and true. Optional 069 * 070 * Query Parameters: 071 * 072 * 073 * rev (string) – Actual document’s revision 074 * batch (string) – Stores document in batch mode Possible values: ok. Optional 075 * 076 * Response Headers: 077 * 078 * 079 * Content-Type – 080 * application/json 081 * text/plain; charset=utf-8 082 * ETag – Double quoted document’s new revision 083 * 084 * Response JSON Object: 085 * 086 * 087 * id (string) – Document ID 088 * ok (boolean) – Operation status 089 * rev (string) – Revision MVCC token 090 * 091 * Status Codes: 092 * 093 * 200 OK – Document successfully removed 094 * 202 Accepted – Request was accepted, but changes are not yet stored on disk 095 * 400 Bad Request – Invalid request body or parameters 096 * 401 Unauthorized – Write privileges required 097 * 404 Not Found – Specified database or document ID doesn’t exists 098 * 409 Conflict – Specified revision is not the latest for target document 099 * 100 * Request: 101 * 102 * </pre> 103 * 104 * @author Alisson Gomes 105 * @since 0.6.0 106 * 107 */ 108public class DeleteCommand extends AbstractCommand implements CouchCommand 109{ 110 private static final Logger LOG = LoggerFactory.getLogger(DeleteCommand.class); 111 private static final Logger LOGSQL = net.sf.jkniv.whinstone.couchdb.LoggerFactory.getLogger(); 112 private Queryable queryable; 113 private HttpBuilder httpBuilder; 114 115 public DeleteCommand(HttpBuilder httpBuilder, Queryable queryable) 116 { 117 super(); 118 this.httpBuilder = httpBuilder; 119 this.queryable = queryable; 120 this.method = HttpMethod.PUT; 121 this.body = JsonMapper.mapper(queryable.getParams()); 122 } 123 124 @SuppressWarnings("unchecked") 125 @Override 126 public <T> T execute() 127 { 128 String json = null; 129 CloseableHttpResponse response = null; 130 //Map<String, Object> answer = null; 131 T answer = null; 132 try 133 { 134 CloseableHttpClient httpclient = HttpClients.createDefault(); 135 String url = httpBuilder.getUrlForAddOrUpdateOrDelete(queryable); 136 HttpDelete http = null; 137 http = (HttpDelete)asDelete().newHttp(url); 138 //http.setEntity( getEntity() ); 139 140 // FIXME supports header request for PUT commands -> Headers: "If-Match", "X-Couch-Full-Commit" 141 httpBuilder.setHeader(http); 142 http.addHeader("If-Match", getRevision(queryable)); 143 printRequest(http); 144 response = httpclient.execute(http); 145 json = EntityUtils.toString(response.getEntity()); 146 printResponse(response, json); 147 148 int statusCode = response.getStatusLine().getStatusCode(); 149 if (isOk(statusCode)) 150 { 151 processResponse(queryable, json); 152 answer = (T)Integer.valueOf("1"); 153 } 154 else if (isAccepted(statusCode)) 155 { 156 LOG.info("Document data accepted, but not yet stored on disk"); 157 processResponse(queryable, json); 158 answer = (T)Integer.valueOf("1"); 159 } 160 else if (isNotFound(statusCode)) 161 { 162 answer = (T)Integer.valueOf("0"); 163 // 204 No Content, 304 Not Modified, 205 Reset Content, 404 Not Found 164 LOG.warn(errorFormat(http, response.getStatusLine(), json)); 165 } 166 else 167 { 168 Map<String,String> result = JsonMapper.mapper(json, Map.class); 169 String reason = result.get("reason"); 170 LOG.error(errorFormat(http, response.getStatusLine(), json)); 171 throw new RepositoryException(response.getStatusLine().toString() +", "+ reason); 172 } 173 //commandHandler.postCommit(); 174 } 175 catch (Exception e) // ClientProtocolException | JsonParseException | JsonMappingException | IOException 176 { 177 //commandHandler.postException(); 178 handlerException.handle(e); 179 } 180 finally 181 { 182 if (response != null) 183 { 184 try 185 { 186 response.close(); 187 } 188 catch (IOException e) 189 { 190 handlerException.handle(e); 191 } 192 } 193 } 194 return answer; 195 } 196 197 private void processResponse(Queryable queryable, String json) 198 { 199 Map<String, Object> response = JsonMapper.mapper(json, Map.class); 200 //Updateable sql = queryable.getDynamicSql().asUpdateable(); 201 Object params = queryable.getParams(); 202 ObjectProxy<?> proxy = ObjectProxyFactory.of(params); 203 PropertyAccess accessId = queryable.getDynamicSql().getSqlDialect().getAccessId(); 204 PropertyAccess accessRev= queryable.getDynamicSql().getSqlDialect().getAccessRevision(); 205 String id = (String) response.get(accessId.getFieldName()); 206 String rev = (String) response.get(accessRev.getFieldName()); 207 if (params instanceof Map) 208 { 209 if (!((Map) params).containsKey(accessId.getFieldName())) 210 ((Map) params).put(accessId.getFieldName(), id); 211 212 ((Map) params).put(accessRev.getFieldName(), rev); 213 } 214 else 215 { 216 if(proxy.hasMethod(accessId.getWriterMethodName())) 217 proxy.invoke(accessId.getWriterMethodName(), id); 218 if(proxy.hasMethod(accessRev.getWriterMethodName())) 219 proxy.invoke(accessRev.getWriterMethodName(), rev); 220 } 221 } 222 223 224 @Override 225 public String getBody() 226 { 227 return this.body; 228 } 229 230 @Override 231 public HttpMethod asDelete() 232 { 233 this.method = HttpMethod.DELETE; 234 return this.method; 235 } 236}