/*
* Copyright 2013 David Tinker
*
* 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 io.qdb.server.controller;
import io.qdb.kvstore.KeyValueStoreException;
import io.qdb.server.databind.DataBinder;
import io.qdb.server.databind.DataBindingException;
import io.qdb.server.databind.DurationParser;
import org.simpleframework.http.Request;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
/**
* Base class for controllers that provide CRUD for some resource.
*/
public abstract class CrudController implements Controller {
protected final JsonService jsonService;
private static final SecureRandom RND = new SecureRandom();
protected CrudController(JsonService jsonService) {
this.jsonService = jsonService;
}
public void handle(Call call) throws IOException {
try {
String id = call.nextSegment();
if (id == null) {
if (call.isGet()) {
if (call.getBoolean("count")) {
count(call);
} else {
int offset = call.getInt("offset", 0);
list(call, offset, call.getInt("limit", Integer.MAX_VALUE - offset));
}
} else if (call.isPost()) {
create(call);
} else {
call.setCode(400);
}
} else {
String resource = call.nextSegment();
if (resource == null) {
if (call.isGet()) show(call, id);
else if (call.isPost() || call.isPut()) createOrUpdate(call, id);
else if (call.isDelete()) delete(call, id);
else call.setCode(400);
} else {
getController(call, id, resource).handle(call);
}
}
} catch (DataBindingException e) {
call.setCode(422, e.getErrors());
} catch (BadRequestException e) {
call.setCode(e.getStatus(), e.getMessage());
} catch (KeyValueStoreException e) {
call.setCode(400, e.getMessage());
}
}
protected void list(Call call, int offset, int limit) throws IOException {
call.setCode(404);
}
protected void count(Call call) throws IOException {
call.setCode(400, "Count not supported");
}
protected void show(Call call, String id) throws IOException {
call.setCode(404);
}
protected void create(Call call) throws IOException {
call.setCode(400, "Create not supported");
}
protected void createOrUpdate(Call call, String id) throws IOException {
call.setCode(400, "Create not supported");
}
protected void update(Call call, String id) throws IOException {
call.setCode(400, "Update not supported");
}
protected void delete(Call call, String id) throws IOException {
call.setCode(400, "Delete not supported");
}
protected Controller getController(Call call, String id, String resource) throws IOException {
return StatusCodeController.SC_404;
}
/**
* Create an instance of cls from the body (if it is JSON) or the form parameters otherwise.
* Throws BadRequestException or DataBindingException for invalid fields or JSON.
*/
protected <T> T getBodyObject(Call call, Class<T> cls) throws IOException {
Request req = call.getRequest();
T dto;
if ("application/json".equals(req.getValue("Content-Type"))) {
InputStream ins = req.getInputStream();
try {
dto = jsonService.fromJson(ins, cls);
} catch (IllegalArgumentException x) {
throw new BadRequestException(400, x.getMessage());
} finally {
ins.close();
}
} else { // assume form data and populate instance with params that match field names of the dto
try {
dto = cls.newInstance();
} catch (Exception x) {
throw new RuntimeException(x.toString(), x);
}
new DataBinder(jsonService).bind(req.getForm(), dto).check();
}
return dto;
}
protected int convertDuration(Object v) throws IllegalArgumentException {
if (v instanceof Number) return ((Number)v).intValue();
if (v instanceof String) return DurationParser.parse((String) v);
throw new IllegalArgumentException("Expected duration");
}
protected String generateId() {
return Integer.toString(Math.abs(RND.nextInt()), 36);
}
public static class Count {
public int count;
public Count(int count) {
this.count = count;
}
}
}