@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
  }
  /**
   * Add record to the database with the provided values. Response should be a JSON formatted
   * collection with #success and #record fields indicating status of request and generated keys (if
   * appropriate) for new record.
   *
   * @param ds data source to be updated
   * @param request request object containing parameters such as data source, field values, etc.
   */
  @RequestMapping(
      value = "/add",
      method = {POST, PUT}) // POST and PUT b/c we're very un-RESTful
  @ResponseBody
  public Map<String, Object> add(
      @RequestParam("dsId") JdbcOeDataSource ds, final HttpServletRequest request)
      throws ErrorMessageException, OeDataSourceAccessException, IOException {
    JdbcOeDataEntrySource jdes = (JdbcOeDataEntrySource) ds;
    Set<String> pks = jdes.getParentTableDetails().getPks();

    // get parent dimensions/values
    Map<String, Dimension> dimensions = new HashMap<String, Dimension>();
    Map<String, Object> values = new HashMap<String, Object>();
    for (Dimension dimension : jdes.getEditDimensions()) {
      dimensions.put(dimension.getId(), dimension);

      // Auto generated, special sql, and pk dimensions are not required on adds
      boolean isRequired =
          (jdes.getAutoGeneratedDimension(dimension.getId()) == null
              && jdes.getSpecialSqlDimension(dimension.getId()) == null
              && pks.contains(dimension.getId()));

      values.putAll(
          ControllerUtils.formatData(
              dimension.getId(),
              request.getParameter(dimension.getId()),
              dimension.getSqlType(),
              isRequired));
    }

    CompleteRecord completeRecord =
        new CompleteRecord(
            new TableAwareQueryRecord(jdes.getTableName(), pks, dimensions, values),
            ControllerUtils.getChildRecordSets(jdes, request, true));

    return jdes.addCompleteRecord(completeRecord, false);
  }