@RequestMapping(value = "/data", method = GET)
  @ResponseBody
  public Map<String, Object> data(@RequestParam("dsId") JdbcOeDataSource ds, WebRequest request)
      throws ErrorMessageException, OeDataSourceAccessException {
    JdbcOeDataEntrySource jdes = (JdbcOeDataEntrySource) ds;
    DbKeyValMap dbKeyValMap = new DbKeyValMap();
    String doNotParseKeys = request.getParameter("doNotParseKeys");
    if (doNotParseKeys == null || !doNotParseKeys.equalsIgnoreCase("true")) {
      dbKeyValMap = ControllerUtils.parseKeyValueMap(jdes, request.getParameterMap());
    }
    // retrieve existing record and children
    CompleteRecord completeRecord =
        jdes.getCompleteRecord(
            dbKeyValMap, new ArrayList<String>(jdes.getChildTableMap().keySet()));

    Map<String, Object> data =
        ControllerUtils.mapDataAndFormatTimeForResponse(
            completeRecord.getParentRecord().getValues().keySet(),
            completeRecord.getParentRecord().getValues());

    // Children
    for (ChildRecordSet childRecordSet : completeRecord.getChildrenRecordSets()) {
      List<Object> childRecords = new ArrayList<Object>();
      for (TableAwareQueryRecord tableAwareQueryRecord : childRecordSet.getChildRecords()) {
        childRecords.add(
            ControllerUtils.mapDataAndFormatTimeForResponse(
                tableAwareQueryRecord.getValues().keySet(), tableAwareQueryRecord.getValues()));
      }
      data.put(childRecordSet.getChildTableName(), childRecords);
    }

    return data;
  }
  @RequestMapping(
      value = "/update",
      method = {POST, PUT})
  @ResponseBody
  public Map<String, Object> update(
      @RequestParam("dsId") JdbcOeDataSource ds,
      WebRequest request,
      HttpServletRequest servletRequest)
      throws ErrorMessageException, OeDataSourceAccessException, IOException {
    JdbcOeDataEntrySource jdes = (JdbcOeDataEntrySource) ds;

    // find primary keys for record
    DbKeyValMap dbKeyValMap = ControllerUtils.parseKeyValueMap(jdes, request.getParameterMap());

    // retrieve existing record and children
    CompleteRecord completeRecord =
        jdes.getCompleteRecord(
            dbKeyValMap, new ArrayList<String>(jdes.getChildTableMap().keySet()));

    // Option to only update parameter values on the completeRecord that
    // are included as part of the request (when merge parameter is true)
    // Defaults to false (nullify parameter values not included on request)
    boolean merge = Boolean.valueOf(request.getParameter("merge"));

    // parent record's values are replaced with request param values
    for (String field : completeRecord.getParentRecord().getValues().keySet()) {
      String parameter = request.getParameter(field);
      TableAwareQueryRecord parentRecord = completeRecord.getParentRecord();
      if (parameter != null) {
        parentRecord
            .getValues()
            .putAll(
                ControllerUtils.formatData(
                    field,
                    parameter,
                    parentRecord.getEditDimensions().get(field).getSqlType(),
                    dbKeyValMap.keySet().contains(field)));
      } else if (!merge) {
        // NEEDS additional flags for data sources using default input panels
        // nullify parameter values on the complete record, if it is an edit dimension
        parentRecord.getValues().put(field, null);
      }
    }

    // remove existing children
    for (ChildRecordSet childRecordSet : completeRecord.getChildrenRecordSets()) {
      childRecordSet.removeAllChildRecords();
    }

    completeRecord.setChildrenRecordSets(
        ControllerUtils.getChildRecordSets(jdes, servletRequest, false));
    jdes.updateCompleteRecord(dbKeyValMap, completeRecord);

    Map<String, Object> data = data(ds, request); // new HashMap<String, Object>();
    data.put("success", true);
    return data; // TODO return RESTful response, i.e. data actually updated
  }
  /**
   * @param ds data source ID sent on URL
   * @param body JSON POST body
   */
  @RequestMapping(
      value = "/delete",
      // FIXME this should obviously be DELETE, but we send an entity body
      method = POST)
  @ResponseBody
  public Map<String, Object> delete(
      @RequestParam("dsId") JdbcOeDataSource ds, @RequestBody DeleteRequest body)
      throws IOException, ErrorMessageException, OeDataSourceAccessException {
    JdbcOeDataEntrySource jdes = (JdbcOeDataEntrySource) ds;
    List<DbKeyValMap> pksForDeletion = new ArrayList<DbKeyValMap>();

    for (Map<String, String> pks : body.getPkIds()) {
      pksForDeletion.add(ControllerUtils.parseKeyValueMap(jdes, pks));
    }

    jdes.deleteQueryRecords(jdes.getTableName(), pksForDeletion);

    // Build/write response
    Map<String, Object> data = new HashMap<String, Object>();
    data.put("success", true);
    return data; // TODO return RESTful response, i.e. data actually deleted
  }