content: /**
   * Copyright (C) 2010 EdgyTech LLC.
   *
   * 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 com.edgytech.umongo;
  
  import com.edgytech.swingfast.*;
  import com.mongodb.BasicDBList;
  import com.mongodb.BasicDBObject;
  import com.mongodb.CommandResult;
  import com.mongodb.DB;
  import com.mongodb.DBCollection;
  import com.mongodb.DBCursor;
  import com.mongodb.DBObject;
  import com.mongodb.GroupCommand;
  import com.mongodb.LazyDBDecoder;
  import com.mongodb.MapReduceCommand;
  import com.mongodb.MapReduceCommand.OutputType;
  import com.mongodb.MapReduceOutput;
  import com.mongodb.Mongo;
  import com.mongodb.MongoException.DuplicateKey;
  import com.mongodb.WriteConcern;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.logging.Level;
  import javax.swing.JPanel;
  import com.edgytech.umongo.CollectionPanel.Item;
  import com.mongodb.BasicDBObjectBuilder;
  import com.mongodb.MongoClient;
  import com.mongodb.WriteResult;
  import com.mongodb.util.JSON;
  import java.util.Map.Entry;
  
  /**
   *
   * @author antoine
   */
  public class CollectionPanel extends BasePanel implements EnumListener<Item> {
  
      enum Item {
  
          icon,
          name,
          fullName,
          queryOptions,
          writeConcern,
          readPreference,
          stats,
          getStats,
          getIndexes,
          refresh,
          dropCollection,
          rename,
          newName,
          dropTarget,
          doImport,
          export,
          find,
          findQuery,
          findFields,
          findSort,
          findSkip,
          findLimit,
          findBatchSize,
          findExplain,
          findExport,
          findHint,
          foQuery,
          foFields,
          count,
          countQuery,
          countSkip,
          countLimit,
          remove,
          removeQuery,
          rmAllConfirm,
          mapReduce,
          mrMap,
          mrReduce,
          mrFinalize,
          mrQuery,
          mrSort,
          mrType,
          mrOut,
          mrOutDB,
          mrOutSharded,
          mrNonAtomic,
          mrLimit,
          mrJSMode,
          group,
          grpKeys,
          grpQuery,
          grpInitialValue,
          grpReduce,
          grpFinalize,
          readWriteOptions,
          insert,
          insertDoc,
          insertCount,
          insertBulk,
          createIndex,
          findAndModify,
          famQuery,
          famFields,
          famSort,
          famUpdate,
          famRemove,
          famReturnNew,
          famUpsert,
          update,
          upQuery,
          upUpdate,
          upUpsert,
          upMulti,
          upSafe,
          save,
          saveDoc,
          shardingInfo,
          shardingDistribution,
          shardCollection,
          shardKeyCombo,
          shardCustomKey,
          shardUniqueIndex,
          listChunks,
          validate,
          validateFull,
          touch,
          touchData,
          touchIndex,
          compact,
          compactForce,
          reIndex,
          moveChunk,
          mvckQuery,
          mvckToShard,
          splitChunk,
          spckQuery,
          spckOnValue,
          geoNear,
          gnOrigin,
          gnLimit,
          gnMaxDistance,
          gnDistanceMultiplier,
          gnQuery,
          gnSpherical,
          gnSearch,
          lazyDecoding,
          fixCollection,
          fcDialog,
          fcSrcMongo,
          fcUpsert,
          fullTextSearch,
          ftsSearch,
          ftsFilter,
          ftsProject,
          ftsLimit,
          ftsLanguage,
          aggregate,
          settings,
          usePowerOf2Sizes,
          manageTagRanges,
          tagRangeList,
          tagRangeDialog,
          addTagRange,
          editTagRange,
          removeTagRange,
          distinct,
          distinctKey,
          distinctQuery,
          summarizeData
      }
  
      public CollectionPanel() {
          setEnumBinding(Item.values(), this);
      }
  
      public CollectionNode getCollectionNode() {
          return (CollectionNode) getNode();
      }
  
      public BasicDBObject getStats() {
          return getCollectionNode().getStats();
      }
  
      @Override
      protected void updateComponentCustom(JPanel old) {
          try {
              DBCollection collection = getCollectionNode().getCollection();
              setStringFieldValue(Item.name, collection.getName());
              setStringFieldValue(Item.fullName, collection.getFullName());
              setStringFieldValue(Item.queryOptions, MongoUtils.queryOptionsToString(collection.getOptions()));
              ((DocField) getBoundUnit(Item.writeConcern)).setDoc(collection.getWriteConcern().getCommand());
              ((DocField) getBoundUnit(Item.readPreference)).setDoc(collection.getReadPreference().toDBObject());
              ((CmdField) getBoundUnit(Item.stats)).updateFromCmd(collection);
          } catch (Exception e) {
              UMongo.instance.showError(this.getClass().getSimpleName() + " update", e);
          }
      }
  
      @Override
      public void actionPerformed(Item enm, XmlComponentUnit unit, Object src) {
          if (enm == Item.lazyDecoding) {
              boolean lazy = getBooleanFieldValue(Item.lazyDecoding);
              DBCollection col = getCollectionNode().getCollection();
              if (lazy) {
                  col.setDBDecoderFactory(LazyDBDecoder.FACTORY);
              } else {
                  col.setDBDecoderFactory(null);
              }
          }
      }
  
      public void find(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final DBObject query = ((DocBuilderField) getBoundUnit(Item.findQuery)).getDBObject();
          final DBObject fields = ((DocBuilderField) getBoundUnit(Item.findFields)).getDBObject();
          final DBObject sort = ((DocBuilderField) getBoundUnit(Item.findSort)).getDBObject();
          final DBObject hint = ((DocBuilderField) getBoundUnit(Item.findHint)).getDBObject();
          final int skip = getIntFieldValue(Item.findSkip);
          final int limit = getIntFieldValue(Item.findLimit);
          final int batchSize = getIntFieldValue(Item.findBatchSize);
          final boolean explain = getBooleanFieldValue(Item.findExplain);
          final boolean export = getBooleanFieldValue(Item.findExport);
  
          if (export) {
              exportToFile(col, query, fields, sort, skip, limit, batchSize);
          } else {
              new DbJob() {
                  @Override
                  public Object doRun() {
                      // this does not actually block, may not need dbjob
                      DBCursor cur = col.find(query, fields, skip, batchSize);
                      if (sort != null) {
                          cur.sort(sort);
                      }
                      if (limit > 0) {
                          cur.limit(limit);
                      }
                      if (hint != null) {
                          cur.hint(hint);
                      }
                      if (explain) {
                          return cur.explain();
                      }
  
                      // force cursor to start
                      cur.hasNext();
                      return cur;
                  }
  
                  @Override
                  public String getNS() {
                      return col.getFullName();
                  }
  
                  @Override
                  public String getShortName() {
                      return "Find";
                  }
  
                  @Override
                  public DBObject getRoot(Object result) {
                      if (result == null || !(result instanceof DBCursor)) {
                          return null;
                      }
                      DBCursor res = (DBCursor) result;
                      BasicDBObject obj = new BasicDBObject("cursorId", res.getCursorId());
                      obj.put("server", res.getServerAddress().toString());
                      obj.put("query", res.getQuery());
                      obj.put("fields", res.getKeysWanted());
                      obj.put("options", res.getOptions());
                      obj.put("readPreference", res.getReadPreference().toDBObject());
                      obj.put("numSeen", res.numSeen());
                      obj.put("numGetMores", res.numGetMores());
                      // want skip, limit, batchsize
                      return obj;
                  }
  
                  @Override
                  public ButtonBase getButton() {
                      return button;
                  }
              }.addJob();
  
          }
      }
  
  //    public void findOne(final ButtonBase button) {
  //        final DBCollection col = getCollectionNode().getCollection();
  //        final DBObject query = ((DocBuilderField) getBoundUnit(Item.foQuery)).getDBObject();
  //        final DBObject fields = ((DocBuilderField) getBoundUnit(Item.foFields)).getDBObject();
  //
  //        new DbJob() {
  //
  //            @Override
  //            public Object doRun() {
  //                return col.findOne(query, fields);
  //            }
  //
  //            @Override
  //            public String getNS() {
  //                return col.getFullName();
  //            }
  //
  //            @Override
  //            public String getShortName() {
  //                return "FindOne";
  //            }
  //
  //            @Override
  //            public DBObject getRoot(Object result) {
  //                DBObject root = new BasicDBObject("query", query);
  //                root.put("fields", fields);
  //                return root;
  //            }
  //
  //            @Override
  //            public ButtonBase getButton() {
  //                return button;
  //            }
  //        }.addJob();
  //    }
      public void rename(ButtonBase button) {
          final CollectionNode colNode = getCollectionNode();
          final DBCollection col = colNode.getCollection();
          // select parent since this is getting renamed
          UMongo.instance.displayNode(colNode.getDbNode());
  
          final String name = getStringFieldValue(Item.newName);
          final boolean dropTarget = getBooleanFieldValue(Item.dropTarget);
  
          DBObject cmd = BasicDBObjectBuilder.start()
                  .add("renameCollection", col.getFullName())
                  .add("to", col.getDB().getName() + "." + name)
                  .add("dropTarget", dropTarget)
                  .get();
          new DbJobCmd(col.getDB().getSisterDB("admin"), cmd, null, null).addJob();
      }
  
      public void group(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          DBObject keys = ((DocBuilderField) getBoundUnit(Item.grpKeys)).getDBObject();
          DBObject initial = ((DocBuilderField) getBoundUnit(Item.grpInitialValue)).getDBObject();
          DBObject query = ((DocBuilderField) getBoundUnit(Item.grpQuery)).getDBObject();
          String reduce = getStringFieldValue(Item.grpReduce);
          String finalize = getStringFieldValue(Item.grpFinalize);
          final GroupCommand cmd = new GroupCommand(col, keys, query, initial, reduce, finalize);
  //        new DocView(null, "Group", col.getDB(), cmd.toDBObject()).addToTabbedDiv();
  
          new DbJobCmd(col.getDB(), cmd.toDBObject(), null, button).addJob();
      }
      
      public void distinct(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final BasicDBObject cmd = new BasicDBObject("distinct", col.getName());
          cmd.put("key", getStringFieldValue(Item.distinctKey));
          DBObject query = ((DocBuilderField) getBoundUnit(Item.distinctQuery)).getDBObject();
          if (query != null)
              cmd.put("query", query);
          new DbJobCmd(col.getDB(), cmd, null, button).addJob();
      }
  
      public void mapReduce(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          String map = getStringFieldValue(Item.mrMap);
          String reduce = getStringFieldValue(Item.mrReduce);
          String finalize = getStringFieldValue(Item.mrFinalize);
          String stype = getStringFieldValue(Item.mrType);
          final OutputType type = OutputType.valueOf(stype.toUpperCase());
          String out = getStringFieldValue(Item.mrOut);
          if (type != OutputType.INLINE && (out.isEmpty())) {
              new InfoDialog(id, null, null, "Output collection cannot be empty if type is not inline.").show();
              return;
          }
  
          String outDB = getStringFieldValue(Item.mrOutDB);
          DBObject query = ((DocBuilderField) getBoundUnit(Item.mrQuery)).getDBObject();
          int limit = getIntFieldValue(Item.mrLimit);
          final MapReduceCommand cmd = new MapReduceCommand(col, map, reduce, out, type, query);
          DBObject sort = ((DocBuilderField) getBoundUnit(Item.mrSort)).getDBObject();
          if (sort != null) {
              cmd.setSort(sort);
          }
          if (!outDB.isEmpty()) {
              cmd.setOutputDB(outDB);
          }
          if (!finalize.isEmpty()) {
              cmd.setFinalize(finalize);
          }
          if (limit > 0) {
              cmd.setLimit(limit);
          }
  
          if (getBooleanFieldValue(Item.mrJSMode)) {
              cmd.addExtraOption("jsMode", true);
          }
  
          final BasicDBObject cmdobj = (BasicDBObject) cmd.toDBObject();
          if (getBooleanFieldValue(Item.mrOutSharded)) {
              ((BasicDBObject) cmdobj.get("out")).put("sharded", true);
          }
          if (getBooleanFieldValue(Item.mrNonAtomic)) {
              ((BasicDBObject) cmdobj.get("out")).put("nonAtomic", true);
          }
  
          new DbJob() {
              MapReduceOutput output;
  
              @Override
              public Object doRun() {
  //                output = col.mapReduce(cmd);
  
                  // if type in inline, then query options like slaveOk is fine
                  CommandResult res = null;
                  if (type == MapReduceCommand.OutputType.INLINE) {
                      res = col.getDB().command(cmdobj, col.getOptions());
                      return res;
                  }
  
                  res = col.getDB().command(cmdobj);
                  res.throwOnError();
                  output = new MapReduceOutput(col, cmdobj, res);
                  return output;
              }
  
              @Override
              public void wrapUp(Object res) {
                  if (output != null) {
                      if (cmd.getOutputType() == OutputType.INLINE) {
                          res = output.results();
                      } else {
                          // spawn a find
                          doFind(output.getOutputCollection(), null);
                          res = output.getRaw();
                      }
                  }
                  super.wrapUp(res);
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "MR";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  return cmdobj;
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
  
              @Override
              DBObject getCommand() {
                  return cmdobj;
              }
  
              @Override
              DB getDB() {
                  return col.getDB();
              }
          }.addJob();
      }
  
      static void doFind(final DBCollection col, final DBObject query) {
          doFind(col, query, null, null, 0, 0, 0, false, null, 0);
      }
  
      static void doFind(final DBCollection col, final DBObject query, final DBObject fields, final DBObject sort, final int skip, final int limit, final int batchSize, final boolean explain, final DBObject hint, final int options) {
          new DbJob() {
              @Override
              public Object doRun() {
                  // this does not actually block, may not need dbjob
                  DBCursor cur = col.find(query, fields).skip(skip).batchSize(batchSize).addOption(options);
                  if (sort != null) {
                      cur.sort(sort);
                  }
                  if (limit > 0) {
                      cur.limit(limit);
                  }
                  if (hint != null) {
                      cur.hint(hint);
                  }
                  if (explain) {
                      return cur.explain();
                  }
  
                  // force cursor to start
                  cur.hasNext();
                  return cur;
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Find";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  if (result == null || !(result instanceof DBCursor)) {
                      return null;
                  }
                  DBCursor res = (DBCursor) result;
                  BasicDBObject obj = new BasicDBObject("cursorId", res.getCursorId());
                  obj.put("query", res.getQuery());
                  obj.put("fields", res.getKeysWanted());
                  obj.put("options", res.getOptions());
                  obj.put("readPreference", res.getReadPreference().toDBObject());
                  obj.put("numSeen", res.numSeen());
                  obj.put("numGetMores", res.numGetMores());
                  // want skip, limit, batchsize
                  return obj;
              }
          }.addJob();
      }
  
      private void exportToFile(final DBCollection col, final DBObject query, final DBObject fields, final DBObject sort, final int skip, final int limit, final int batchSize) {
          ExportDialog dia = UMongo.instance.getGlobalStore().getExportDialog();
          if (!dia.show()) {
              return;
          }
          final DocumentSerializer ds = dia.getDocumentSerializer();
          final boolean continueOnError = dia.getBooleanFieldValue(ExportDialog.Item.continueOnError);
          new DbJob() {
              @Override
              public Object doRun() throws Exception {
                  try {
                      try {
                          DBCursor cur = col.find(query, fields);
                          if (skip > 0) {
                              cur.skip(skip);
                          }
                          if (batchSize != 0) {
                              cur.batchSize(batchSize);
                          }
                          if (sort != null) {
                              cur.sort(sort);
                          }
                          if (limit > 0) {
                              cur.limit(limit);
                          }
                          while (cur.hasNext() && !stopped) {
                              ds.writeObject(cur.next());
                          }
                      } catch (Exception e) {
                          if (continueOnError) {
                              getLogger().log(Level.WARNING, null, e);
                          } else {
                              throw e;
                          }
                      }
                  } finally {
                      ds.close();
                  }
                  return null;
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Export";
              }
          }.addJob();
      }
  
  //    static void doFindOne(final DBCollection col, final DBObject query, final DBObject fields) {
  //        new DbJob() {
  //
  //            @Override
  //            public Object doRun() {
  //                return col.findOne(query, fields);
  //            }
  //
  //            @Override
  //            public String getNS() {
  //                return col.getFullName();
  //            }
  //
  //            @Override
  //            public String getShortName() {
  //                return "FindOne";
  //            }
  //
  //            @Override
  //            public DBObject getRoot(Object result) {
  //                BasicDBObject obj = new BasicDBObject("query", query);
  //                obj.put("fields", fields);
  //                return obj;
  //            }
  //        }.addJob();
  //    }
      public void dropCollection(ButtonBase button) {
          final CollectionNode colNode = getCollectionNode();
          final DBCollection col = getCollectionNode().getCollection();
          BasicDBObject cmd = new BasicDBObject( "drop" , col.getName() );
          new DbJobCmd(col.getDB(), cmd, null, colNode.getDbNode(), null).addJob();
      }
  
      public void readWriteOptions(ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          OptionDialog od = UMongo.instance.getGlobalStore().getOptionDialog();
          od.update(col.getOptions(), col.getWriteConcern(), col.getReadPreference());
          if (!od.show()) {
              return;
          }
          col.setOptions(od.getQueryOptions());
          col.setWriteConcern(od.getWriteConcern());
          col.setReadPreference(od.getReadPreference());
          refresh();
      }
  
      public void insert(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final BasicDBObject doc = (BasicDBObject) ((DocBuilderField) getBoundUnit(Item.insertDoc)).getDBObject();
          final int count = getIntFieldValue(Item.insertCount);
          final boolean bulk = getBooleanFieldValue(Item.insertBulk);
  
          new DbJob() {
              @Override
              public Object doRun() throws IOException {
                  WriteResult res = null;
                  List<DBObject> list = new ArrayList<DBObject>();
                  for (int i = 0; i < count; ++i) {
                      BasicDBObject newdoc = (BasicDBObject) doc.copy();
                      handleSpecialFields(newdoc);
                      if (bulk) {
                          list.add(newdoc);
                          if (list.size() >= 1000) {
                              res = col.insert(list);
                              list.clear();
                          }
                      } else {
                          res = col.insert(newdoc);
                      }
                  }
                  if (bulk && !list.isEmpty()) {
                      return col.insert(list);
                  }
                  return res;
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Insert";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject root = new BasicDBObject("doc", doc);
                  root.put("count", count);
                  root.put("bulk", bulk);
                  return root;
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
          }.addJob();
      }
  
      public void save(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final BasicDBObject doc = (BasicDBObject) ((DocBuilderField) getBoundUnit(Item.saveDoc)).getDBObject();
  
          new DbJob() {
              @Override
              public Object doRun() throws IOException {
                  return col.save((DBObject) doc.copy());
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Save";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  return doc;
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
          }.addJob();
      }
  
      public void remove(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final BasicDBObject tmp = (BasicDBObject) ((DocBuilderField) getBoundUnit(Item.removeQuery)).getDBObject();
          final BasicDBObject doc = tmp != null ? tmp : new BasicDBObject();
  
          if (doc.isEmpty()) {
              ConfirmDialog confirm = (ConfirmDialog) getBoundUnit(Item.rmAllConfirm);
              if (!confirm.show()) {
                  return;
              }
          }
  
          new DbJob() {
              @Override
              public Object doRun() throws IOException {
                  return col.remove(doc);
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Remove";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  return new BasicDBObject("query", doc);
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
          }.addJob();
      }
  
      public void createIndex(final ButtonBase button) {
          final CollectionNode node = getCollectionNode();
          final DBCollection col = getCollectionNode().getCollection();
          CreateIndexDialog dia = (CreateIndexDialog) button.getDialog();
          final DBObject keys = dia.getKeys();
          final DBObject opts = dia.getOptions();
  
          if (!UMongo.instance.getGlobalStore().confirmLockingOperation()) {
              return;
          }
  
          new DbJob() {
              @Override
              public Object doRun() throws IOException {
  //                opts.put("key", keys);
  //                return col.getDB().getCollection("system.indexes").insert(opts);
  //                col.ensureIndex(keys, opts);
                  col.createIndex(keys, opts);
                  return new BasicDBObject("ok", 1);
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Ensure Index";
              }
  
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  node.structureComponent();
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
              
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject obj = new BasicDBObject("keys", keys);
                  obj.put("options", opts);
                  return obj;
              }
          }.addJob();
      }
  
      public void count(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final DBObject query = ((DocBuilderField) getBoundUnit(Item.countQuery)).getDBObject();
          final int skip = getIntFieldValue(Item.countSkip);
          final int limit = getIntFieldValue(Item.countLimit);
  
          BasicDBObject cmd = new BasicDBObject();
          cmd.put("count", col.getName());
          cmd.put("query", query);
  
          if (limit > 0) {
              cmd.put("limit", limit);
          }
          if (skip > 0) {
              cmd.put("skip", skip);
          }
          new DbJobCmd(col.getDB(), cmd, null, button).addJob();
      }
  
      public void findAndModify(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final DBObject query = ((DocBuilderField) getBoundUnit(Item.famQuery)).getDBObject();
          final DBObject fields = ((DocBuilderField) getBoundUnit(Item.famFields)).getDBObject();
          final DBObject sort = ((DocBuilderField) getBoundUnit(Item.famSort)).getDBObject();
          final BasicDBObject update = (BasicDBObject) ((DocBuilderField) getBoundUnit(Item.famUpdate)).getDBObject();
          final boolean remove = getBooleanFieldValue(Item.famRemove);
          final boolean returnNew = getBooleanFieldValue(Item.famReturnNew);
          final boolean upsert = getBooleanFieldValue(Item.famUpsert);
  
          BasicDBObject cmd = new BasicDBObject("findandmodify", col.getName());
          if (query != null && !query.keySet().isEmpty()) {
              cmd.append("query", query);
          }
          if (fields != null && !fields.keySet().isEmpty()) {
              cmd.append("fields", fields);
          }
          if (sort != null && !sort.keySet().isEmpty()) {
              cmd.append("sort", sort);
          }
  
          if (remove) {
              cmd.append("remove", remove);
          } else {
              if (update != null && !update.keySet().isEmpty()) {
                  // if 1st key doesn't start with $, then object will be inserted as is, need to check it
                  String key = update.keySet().iterator().next();
                  if (key.charAt(0) != '$') {
                      MongoUtils.checkObject(update, false, false);
                  }
                  cmd.append("update", (DBObject) update.copy());
              }
              if (returnNew) {
                  cmd.append("new", returnNew);
              }
              if (upsert) {
                  cmd.append("upsert", upsert);
              }
          }
  
          new DbJobCmd(col.getDB(), cmd, null, button).addJob();
      }
  
      public void update(final ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection();
          final DBObject query = ((DocBuilderField) getBoundUnit(Item.upQuery)).getDBObject();
          final BasicDBObject update = (BasicDBObject) ((DocBuilderField) getBoundUnit(Item.upUpdate)).getDBObject();
          final boolean upsert = getBooleanFieldValue(Item.upUpsert);
          final boolean multi = getBooleanFieldValue(Item.upMulti);
          final boolean safe = getBooleanFieldValue(Item.upSafe);
          col.setWriteConcern(WriteConcern.SAFE);
  
          new DbJob() {
              @Override
              public Object doRun() {
                  if (safe) {
                      long count = col.getCount(query);
                      long toupdate = count > 0 ? 1 : 0;
                      if (multi) {
                          toupdate = count;
                      }
                      String text = "Proceed with updating " + toupdate + " of " + count + " documents?";
                      ConfirmDialog confirm = new ConfirmDialog(null, "Confirm Update", null, text);
                      if (!confirm.show()) {
                          return null;
                      }
                  }
                  return col.update(query, (DBObject) update.copy(), upsert, multi);
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Update";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject obj = new BasicDBObject("query", query);
                  obj.put("update", update);
                  obj.put("upsert", upsert);
                  obj.put("multi", multi);
                  return obj;
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
          }.addJob();
      }
  
      public void doImport(final ButtonBase button) throws IOException {
          ImportDialog dia = UMongo.instance.getGlobalStore().getImportDialog();
          if (!dia.show()) {
              return;
          }
          final boolean dropCollection = dia.getBooleanFieldValue(ImportDialog.Item.dropCollection);
          final boolean continueOnError = dia.getBooleanFieldValue(ImportDialog.Item.continueOnError);
          final boolean upsert = dia.getBooleanFieldValue(ImportDialog.Item.upsert);
          final boolean bulk = dia.getBooleanFieldValue(ImportDialog.Item.bulk);
          String supsertFields = dia.getStringFieldValue(ImportDialog.Item.upsertFields);
          final String[] upsertFields = supsertFields != null ? supsertFields.split(",") : null;
          if (upsertFields != null) {
              for (int i = 0; i < upsertFields.length; ++i) {
                  upsertFields[i] = upsertFields[i].trim();
              }
          }
          final DocumentDeserializer dd = dia.getDocumentDeserializer();
          final DBCollection col = getCollectionNode().getCollection();
  
          new DbJob() {
              @Override
              public Object doRun() throws Exception {
                  try {
                      if (dropCollection) {
                          col.drop();
                      }
                      DBObject obj = null;
                      List<DBObject> batch = new ArrayList<DBObject>();
                      while ((obj = dd.readObject()) != null) {
                          try {
                              if (upsert) {
                                  if (upsertFields == null) {
                                      col.save(obj);
                                  } else {
                                      BasicDBObject query = new BasicDBObject();
                                      for (int i = 0; i < upsertFields.length; ++i) {
                                          String field = upsertFields[i];
                                          if (!obj.containsField(field)) {
                                              throw new Exception("Upsert field " + field + " not present in object " + obj.toString());
                                          }
                                          query.put(field, obj.get(field));
                                      }
                                      col.update(query, obj, true, false);
                                  }
                              } else {
                                  if (bulk) {
                                      if (batch.size() > 1000) {
                                          col.insert(batch);
                                          batch.clear();
                                      }
                                      batch.add(obj);
                                  } else {
                                      col.insert(obj);
                                  }
                              }
                          } catch (Exception e) {
                              if (continueOnError) {
                                  getLogger().log(Level.WARNING, null, e);
                              } else {
                                  throw e;
                              }
                          }
                      }
  
                      if (!batch.isEmpty()) {
                          col.insert(batch);
                      }
  
                  } finally {
                      dd.close();
                  }
                  return null;
              }
  
              @Override
              public String getNS() {
                  return col.getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Import";
              }
  
              @Override
              public ButtonBase getButton() {
                  return button;
              }
          }.addJob();
      }
  
      public void export(ButtonBase button) {
          exportToFile(getCollectionNode().getCollection(), null, null, null, 0, 0, 0);
      }
  
      public void getStats(ButtonBase button) {
          new DbJobCmd(getCollectionNode().getCollection(), "collstats").addJob();
      }
  
      public void getIndexes(ButtonBase button) {
          final DBCollection col = getCollectionNode().getCollection().getDB().getCollection("system.indexes");
          CollectionPanel.doFind(col, new BasicDBObject("ns", getCollectionNode().getCollection().getFullName()));
      }
  
      public void validate(ButtonBase button) {
          BasicDBObject cmd = new BasicDBObject("validate", getCollectionNode().getCollection().getName());
          if (getBooleanFieldValue(Item.validateFull)) {
              cmd.put("full", true);
          }
          new DbJobCmd(getCollectionNode().getDbNode().getDb(), cmd, null, button).addJob();
      }
  
      public void touch(ButtonBase button) {
          BasicDBObject cmd = new BasicDBObject("touch", getCollectionNode().getCollection().getName());
          cmd.put("data", getBooleanFieldValue(Item.touchData));
          cmd.put("index", getBooleanFieldValue(Item.touchIndex));
          new DbJobCmd(getCollectionNode().getDbNode().getDb(), cmd, null, button).addJob();
      }
  
      public void compact(ButtonBase button) {
          BasicDBObject cmd = new BasicDBObject("compact", getCollectionNode().getCollection().getName());
          if (getBooleanFieldValue(Item.compactForce)) {
              cmd.put("force", true);
          }
          if (!UMongo.instance.getGlobalStore().confirmLockingOperation()) {
              return;
          }
          new DbJobCmd(getCollectionNode().getDbNode().getDb(), cmd, null, button).addJob();
      }
  
      public void reIndex(ButtonBase button) {
          BasicDBObject cmd = new BasicDBObject("reIndex", getCollectionNode().getCollection().getName());
          if (!UMongo.instance.getGlobalStore().confirmLockingOperation()) {
              return;
          }
          new DbJobCmd(getCollectionNode().getDbNode().getDb(), cmd, null, button).addJob();
      }
  
      public void shardingInfo(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("collections");
          CollectionPanel.doFind(col, new BasicDBObject("_id", getCollectionNode().getCollection().getFullName()));
      }
  
      public void shardingDistribution(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
  
          new DbJob() {
              @Override
              public Object doRun() throws Exception {
                  BasicDBObject result = new BasicDBObject();
                  BasicDBList shardList = new BasicDBList();
                  BasicDBObject stats = getStats();
                  BasicDBObject shards = (BasicDBObject) stats.get("shards");
                  if (shards == null || shards.isEmpty())
                      return null;
                      
                  long totalChunks = 0;
                  long totalSize = stats.getLong("size");
                  long totalCount = stats.getLong("count");
  
                  for (Entry shard : shards.entrySet()) {
                      String shardName = (String) shard.getKey();
                      BasicDBObject shardStats = (BasicDBObject) shard.getValue();
  
                      BasicDBObject query = new BasicDBObject("ns", getCollectionNode().getCollection().getFullName());
                      query.put("shard", shardName);
                      long numChunks = config.getCollection("chunks").count(query);
                      totalChunks += numChunks;
  
                      double estChunkData = numChunks <= 0 ? 0 : shardStats.getLong("size") / numChunks;
                      long estChunkCount = numChunks <= 0 ? 0 : (long) Math.floor(shardStats.getLong("count") / numChunks);
  
                      BasicDBObject shardDetails = new BasicDBObject("shard", shardName);
                      shardDetails.put("data", shardStats.getLong("size"));
                      shardDetails.put("pctData", totalSize <= 0 ? 0 : (shardStats.getLong("size") * 100.0) / totalSize);
                      shardDetails.put("docs", shardStats.getLong("count"));
                      shardDetails.put("pctDocs", totalCount <= 0 ? 0 : (shardStats.getLong("count") * 100.0) / totalCount);
                      shardDetails.put("chunks", numChunks);
                      if (shardStats.containsField("avgObjSize"))
                      shardDetails.put("avgDocSize", shardStats.getDouble("avgObjSize"));
                      shardDetails.put("estimatedDataPerChunk", estChunkData);
                      shardDetails.put("estimatedDocsPerChunk", estChunkCount);
                      shardList.add(shardDetails);
                  }
                  result.put("shards", shardList);
  
                  BasicDBObject total = new BasicDBObject();
                  total.put("data", totalSize);
                  total.put("docs", totalCount);
                  total.put("chunks", totalChunks);
                  total.put("avgDocSize", stats.getDouble("avgObjSize"));
                  result.put("total", total);
                  return result;
              }
  
              @Override
              public String getNS() {
                  return getCollectionNode().getCollection().getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Sharding Distribution";
              }
          }.addJob();
      }
  
      public void listChunks(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("chunks");
          CollectionPanel.doFind(col, new BasicDBObject("ns", getCollectionNode().getCollection().getFullName()));
      }
  
      public void moveChunk(ButtonBase button) {
          FormDialog dialog = (FormDialog) ((MenuItem) getBoundUnit(Item.moveChunk)).getDialog();
          ComboBox combo = (ComboBox) getBoundUnit(Item.mvckToShard);
          combo.value = 0;
          combo.items = getCollectionNode().getDbNode().getMongoNode().getShardNames();
          combo.structureComponent();
  
          if (!dialog.show()) {
              return;
          }
  
          BasicDBObject cmd = new BasicDBObject("moveChunk", getCollectionNode().getCollection().getFullName());
          DBObject query = ((DocBuilderField) getBoundUnit(Item.mvckQuery)).getDBObject();
          cmd.append("find", query);
          cmd.append("to", getStringFieldValue(Item.mvckToShard));
          new DbJobCmd(getCollectionNode().getDbNode().getDb().getSisterDB("admin"), cmd).addJob();
  
      }
  
      public void splitChunk(ButtonBase button) {
          BasicDBObject cmd = new BasicDBObject("split", getCollectionNode().getCollection().getFullName());
          DBObject query = ((DocBuilderField) getBoundUnit(Item.spckQuery)).getDBObject();
          if (getBooleanFieldValue(Item.spckOnValue)) {
              cmd.append("middle", query);
          } else {
              cmd.append("find", query);
          }
          new DbJobCmd(getCollectionNode().getDbNode().getDb().getSisterDB("admin"), cmd).addJob();
      }
  
      public void shardCollection(ButtonBase button) {
          FormDialog dialog = (FormDialog) ((MenuItem) getBoundUnit(Item.shardCollection)).getDialog();
          ComboBox combo = (ComboBox) getBoundUnit(Item.shardKeyCombo);
          combo.value = 0;
          List<DBObject> indices = getCollectionNode().getCollection().getIndexInfo();
          String[] items = new String[indices.size() + 1];
          items[0] = "None";
          int i = 1;
          for (DBObject index : indices) {
              items[i++] = ((DBObject) index.get("key")).toString();
          }
          combo.items = items;
          combo.structureComponent();
  
          if (!dialog.show()) {
              return;
          }
  
          DBObject key = null;
          int index = combo.getComponentIntValue();
          if (index > 0) {
              key = (DBObject) indices.get(index - 1).get("key");
          } else {
              key = ((DocBuilderField) getBoundUnit(Item.shardCustomKey)).getDBObject();
          }
  
          if (key == null) {
              new InfoDialog(null, "Empty key", null, "You must select a shard key").show();
              return;
          }
          if (!new ConfirmDialog(null, "Confirm shard key", null, "About to shard collection with key " + key + ", is it correct? This operation cannot be undone.").show()) {
              return;
          }
  
          boolean unique = getBooleanFieldValue(Item.shardUniqueIndex);
          DB admin = getCollectionNode().getDbNode().getDb().getSisterDB("admin");
          DBObject cmd = new BasicDBObject("shardCollection", getCollectionNode().getCollection().getFullName());
          cmd.put("key", key);
          if (unique) {
              cmd.put("unique", unique);
          }
          new DbJobCmd(admin, cmd, this, null).addJob();
  
      }
  
      private Object handleSpecialFields(DBObject doc) {
          for (String field : doc.keySet()) {
              if (field.equals("__rand")) {
                  String type = (String) doc.get(field);
                  if (type.equals("int")) {
                      int min = (Integer) doc.get("min");
                      int max = (Integer) doc.get("max");
                      return min + (int) (Math.random() * ((max - min) + 1));
                  } else if (type.equals("str")) {
                      int len = (Integer) doc.get("len");
                      StringBuilder sb = new StringBuilder(len);
                      byte min = 0x61;
                      byte max = 0x7a;
                      for (int i = 0; i < len; ++i) {
                          char c = (char) (min + (byte) (Math.random() * ((max - min) + 1)));
                          sb.append(c);
                      }
                      return sb.toString();
                  }
              }
              Object val = doc.get(field);
              if (val instanceof BasicDBObject) {
                  BasicDBObject subdoc = (BasicDBObject) val;
                  Object res = handleSpecialFields(subdoc);
                  if (res != null) {
                      doc.put(field, res);
                  }
              } else if (val instanceof BasicDBList) {
                  BasicDBList sublist = (BasicDBList) val;
                  handleSpecialFields(sublist);
              }
          }
          return null;
      }
  
      public void geoNear(ButtonBase button) {
          DBObject cmd = new BasicDBObject("geoNear", getCollectionNode().getCollection().getName());
          DBObject origin = ((DocBuilderField) getBoundUnit(Item.gnOrigin)).getDBObject();
          cmd.put("near", origin);
          double distance = getDoubleFieldValue(Item.gnMaxDistance);
          cmd.put("maxDistance", distance);
          int limit = getIntFieldValue(Item.gnLimit);
          cmd.put("limit", limit);
          double distanceMult = getDoubleFieldValue(Item.gnDistanceMultiplier);
          if (distanceMult > 0) {
              cmd.put("distanceMultiplier", distanceMult);
          }
          DBObject query = ((DocBuilderField) getBoundUnit(Item.gnQuery)).getDBObject();
          if (query != null) {
              cmd.put("query", query);
          }
          boolean spherical = getBooleanFieldValue(Item.gnSpherical);
          if (spherical) {
              cmd.put("spherical", true);
          }
          DBObject search = ((DocBuilderField) getBoundUnit(Item.gnSearch)).getDBObject();
          if (search != null) {
              cmd.put("search", search);
          }
  
          new DbJobCmd(getCollectionNode().getDbNode().getDb(), cmd).addJob();
      }
  
      public void fixCollection(ButtonBase button) {
          final MongoClient m = getCollectionNode().getDbNode().getMongoNode().getMongoClient();
          ArrayList<MongoNode> mongoNodes = UMongo.instance.getMongos();
          String[] mongonames = new String[mongoNodes.size() - 1];
          MongoClient[] mongos = new MongoClient[mongonames.length];
          int i = 0;
          for (MongoNode node : mongoNodes) {
              MongoClient m2 = node.getMongoClient();
              if (m == m2) {
                  continue;
              }
              mongonames[i] = m2.toString();
              mongos[i] = m2;
              ++i;
          }
          ComboBox src = (ComboBox) getBoundUnit(Item.fcSrcMongo);
          src.items = mongonames;
          src.structureComponent();
          FormDialog dialog = (FormDialog) getBoundUnit(Item.fcDialog);
          if (!dialog.show()) {
              return;
          }
  
          final DBCollection dstCol = getCollectionNode().getCollection();
          final MongoClient srcMongo = mongos[src.getIntValue()];
          final boolean upsert = getBooleanFieldValue(Item.fcUpsert);
  
          final String dbname = dstCol.getDB().getName();
          final String colname = dstCol.getName();
          final DBCollection srcCol = srcMongo.getDB(dbname).getCollection(colname);
          String txt = "About to copy from ";
          txt += srcMongo.getConnectPoint() + "(" + srcCol.count() + ")";
          txt += " to ";
          txt += m.getConnectPoint() + "(" + dstCol.count() + ")";
          if (!new ConfirmDialog(null, "Confirm Fix Collection", null, txt).show()) {
              return;
          }
  
          new DbJob() {
              @Override
              public Object doRun() {
                  DBCursor cur = srcCol.find();
                  int count = 0;
                  int dup = 0;
                  while (cur.hasNext()) {
                      DBObject obj = cur.next();
                      if (upsert) {
                          BasicDBObject id = new BasicDBObject("_id", obj.get("_id"));
                          dstCol.update(id, obj, true, false);
                      } else {
                          try {
                              dstCol.insert(obj);
                          } catch (DuplicateKey e) {
                              // dup keys are expected here
                              ++dup;
                          }
                      }
                      ++count;
                  }
                  DBObject res = new BasicDBObject("count", count);
                  res.put("dups", dup);
                  return res;
              }
  
              @Override
              public String getNS() {
                  return "*";
              }
  
              @Override
              public String getShortName() {
                  return "Fix Collection";
              }
          }.addJob();
      }
  
      public void fullTextSearch(final ButtonBase button) {
          final String search = getStringFieldValue(Item.ftsSearch);
          final DBObject filter = ((DocBuilderField) getBoundUnit(Item.ftsFilter)).getDBObject();
          final DBObject project = ((DocBuilderField) getBoundUnit(Item.ftsProject)).getDBObject();
          int limit = getIntFieldValue(Item.ftsLimit);
          final String language = getStringFieldValue(Item.ftsLanguage);
  
          BasicDBObject cmd = new BasicDBObject("text", getCollectionNode().getCollection().getName());
          cmd.put("search", search);
          cmd.put("filter", filter);
          cmd.put("project", project);
          cmd.put("limit", limit);
          cmd.put("language", language);
  
          new DbJobCmd(getCollectionNode().getCollection().getDB(), cmd, null, button).addJob();
      }
  
      public void aggregate(ButtonBase button) {
          AggregateDialog dialog = (AggregateDialog) ((MenuItem) getBoundUnit(Item.aggregate)).getDialog();
          dialog.refreshAggList();
  
          if (!dialog.show()) {
              return;
          }
  
          BasicDBObject cmd = dialog.getAggregateCommand(getCollectionNode().getCollection().getName());
          new DbJobCmd(getCollectionNode().getCollection().getDB(), cmd, null, button).addJob();
      }
  
      public void settings(ButtonBase button) {
          FormDialog dialog = (FormDialog) ((MenuItem) getBoundUnit(Item.settings)).getDialog();
          BasicDBObject stats = (BasicDBObject) getStats();
          boolean pwr2 = stats.getBoolean("userFlags", false);
  
          setBooleanFieldValue(Item.usePowerOf2Sizes, pwr2);
          if (!dialog.show()) {
              return;
          }
  
          boolean newPwr2 = getBooleanFieldValue(Item.usePowerOf2Sizes);
          if (newPwr2 != pwr2) {
              BasicDBObject cmd = new BasicDBObject("collMod", getCollectionNode().getCollection().getName());
              cmd.put("usePowerOf2Sizes", newPwr2);
              new DbJobCmd(getCollectionNode().getCollection().getDB(), cmd).addJob();
          }
      }
      
      void refreshTagRangeList() {
          String ns = getCollectionNode().getCollection().getFullName();
          ListArea list = (ListArea) getBoundUnit(Item.tagRangeList);
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("tags");
          DBCursor cur = col.find(new BasicDBObject("ns", ns));
          
          ArrayList<String> ranges = new ArrayList<String>();
          while (cur.hasNext()) {
              BasicDBObject range = (BasicDBObject) cur.next();
              ranges.add(MongoUtils.getJSON(range));
          }
          list.items = ranges.toArray(new String[ranges.size()]);
          list.structureComponent();
      }
  
      public void manageTagRanges(ButtonBase button) {
          FormDialog dialog = (FormDialog) ((MenuItem) getBoundUnit(Item.manageTagRanges)).getDialog();
          refreshTagRangeList();
          dialog.show();
      }
      
      public void addTagRange(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("tags");
          final String ns = getCollectionNode().getCollection().getFullName();
  
          TagRangeDialog dia = (TagRangeDialog) getBoundUnit(Item.tagRangeDialog);
          dia.resetForNew(config, ns);
          if (!dia.show()) {
              return;
          }
          
          final BasicDBObject doc = dia.getRange(ns);
          
          new DbJob() {
  
              @Override
              public Object doRun() {
                  return col.insert(doc);
              }
  
              @Override
              public String getNS() {
                  return ns;
              }
  
              @Override
              public String getShortName() {
                  return "Add Tag Range";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject root = new BasicDBObject("doc", doc);
                  return root;
              }
              
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  refreshTagRangeList();
              }
          }.addJob();
      }
      
      public void editTagRange(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("tags");
          final String ns = getCollectionNode().getCollection().getFullName();
  
          TagRangeDialog dia = (TagRangeDialog) getBoundUnit(Item.tagRangeDialog);
          String value = getComponentStringFieldValue(Item.tagRangeList);
          BasicDBObject range = (BasicDBObject) JSON.parse(value);
          dia.resetForEdit(config, range);
          if (!dia.show()) {
              return;
          }
          
          final BasicDBObject doc = dia.getRange(ns);
          
          new DbJob() {
  
              @Override
              public Object doRun() {
                  return col.save(doc);
              }
  
              @Override
              public String getNS() {
                  return ns;
              }
  
              @Override
              public String getShortName() {
                  return "Edit Tag Range";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject root = new BasicDBObject("doc", doc);
                  return root;
              }
              
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  refreshTagRangeList();
              }
          }.addJob();
      }
      
      public void removeTagRange(ButtonBase button) {
          final DB config = getCollectionNode().getCollection().getDB().getSisterDB("config");
          final DBCollection col = config.getCollection("tags");
  
          final String ns = getCollectionNode().getCollection().getFullName();
          String value = getComponentStringFieldValue(Item.tagRangeList);
          BasicDBObject range = (BasicDBObject) JSON.parse(value);
          final DBObject min = (DBObject) range.get("min");
  
          final DBObject doc = new BasicDBObject("_id", new BasicDBObject("ns", ns).append("min", min));
  
          new DbJob() {
  
              @Override
              public Object doRun() {
                  return col.remove(doc);
              }
  
              @Override
              public String getNS() {
                  return ns;
              }
  
              @Override
              public String getShortName() {
                  return "Remove Tag Range";
              }
  
              @Override
              public DBObject getRoot(Object result) {
                  BasicDBObject root = new BasicDBObject("doc", doc);
                  return root;
              }
              
              @Override
              public void wrapUp(Object res) {
                  super.wrapUp(res);
                  refreshTagRangeList();
              }
          }.addJob();
      }
  
      public void summarizeData(final ButtonBase button) {
          final CollectionNode cnode = getCollectionNode();
          
          new DbJob() {
              @Override
              public Object doRun() throws IOException {
                  return cnode.summarizeData();
              }
  
              @Override
              public String getNS() {
                  return cnode.getCollection().getFullName();
              }
  
              @Override
              public String getShortName() {
                  return "Summarize Data";
              }
          }.addJob();
  
      }
  }
  