/*
* Copyright 2015 Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.couchbase.mock.http.query;
import org.apache.http.*;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.util.EntityUtils;
import org.couchbase.mock.JsonUtils;
import org.couchbase.mock.httpio.HandlerUtil;
import org.couchbase.mock.util.Base64;
import java.io.IOException;
import java.util.*;
/**
* Designed to handle fake N1QL queries
*/
public class QueryServer implements HttpRequestHandler {
static private volatile int randNumber = new Random().nextInt();
public QueryServer() {
}
/**
* This is the equivalent to 'dropping' indexes
*/
public static void resetIndexState() {
randNumber = new Random().nextInt();
}
static void doError(HttpResponse response, String msg, int code) {
Map<String,Object> mm = new HashMap<String, Object>();
List<Object> ll = new ArrayList<Object>();
Map<String,Object> err = new HashMap<String, Object>();
err.put("msg", msg);
err.put("code", code);
ll.add(err);
mm.put("errors", ll);
String json = JsonUtils.encode(mm);
HandlerUtil.makeJsonResponse(response, json);
}
static private Map<String,Object> resultMeta() {
Map<String,Object> payload = new HashMap<String, Object>();
payload.put("status", "success");
payload.put("results", new ArrayList<Object>());
return payload;
}
static private void addResult(Map<String,Object> meta, Map<String,Object> row) {
List<Object> results = (List)meta.get("results");
results.add(row);
}
private static Map<String,Object> okRow() {
Map<String,Object> mm = new HashMap<String, Object>();
mm.put("row", "value");
return mm;
}
private void handleString(String query, HttpResponse response) {
query = query.toLowerCase();
Map<String,Object> result;
if (query.startsWith("prepare")) {
if (!query.equals("prepare select mockrow")) {
doError(response, "keyspace not found", 12003);
return;
}
// Return the plan and the encoded form...
Map<String, Object> mm = new HashMap<String, Object>();
// This is our "plan"
mm.put("randomNumber", randNumber);
String encoded = Base64.encode(JsonUtils.encode(mm));
mm.put("encoded_plan", encoded);
mm.put("name", "blah-blah-" + new Random().nextLong());
result = mm;
} else if (query.equals("select mockrow")) {
result = okRow();
} else if (query.equals("select emptyrow")) {
// No rows!
result = null;
} else {
doError(response, "keyspace not found", 12003);
return;
}
Map<String,Object> payload = resultMeta();
if (result != null) {
addResult(payload, result);
}
String resStr = JsonUtils.encode(payload);
HandlerUtil.makeJsonResponse(response, resStr);
}
private void handlePrepared(Map<String,Object> body, HttpResponse response) {
// Check the encoded plan
String name = (String) body.get("prepared");
String encoded = (String) body.get("encoded_plan");
if (name == null || encoded == null) {
System.err.println(body.toString());
doError(response, "missing field", 4040);
return;
}
String decoded = Base64.decode(encoded);
if (decoded.isEmpty()) {
doError(response, "could not decode base64", 4070);
return;
}
Map<String,Object> mm = JsonUtils.decodeAsMap(decoded);
int randNum = ((Number) mm.get("randomNumber")).intValue();
if (randNum == randNumber) {
Map<String,Object> payload = resultMeta();
addResult(payload, okRow());
String resStr = JsonUtils.encode(payload);
HandlerUtil.makeJsonResponse(response, resStr);
} else {
doError(response, "index deleted or node hosting the index is down - cause: queryport.indexNotFound", 5000);
}
}
@Override
public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException {
if (!request.getRequestLine().getMethod().equals("POST")) {
response.setStatusCode(HttpStatus.SC_METHOD_NOT_ALLOWED);
return;
}
HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity();
String txt = EntityUtils.toString(entity);
Map<String,Object> mm;
try {
mm = JsonUtils.decodeAsMap(txt);
if (mm == null) {
throw new RuntimeException("Body is empty: " + txt);
}
} catch (Exception ex) {
HandlerUtil.make400Response(response, ex.toString());
return;
}
if (!mm.containsKey("statement")) {
handlePrepared(mm, response);
} else {
handleString((String)mm.get("statement"), response);
}
}
}