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.Application;
  import com.edgytech.swingfast.ConfirmDialog;
  import com.edgytech.swingfast.Frame;
  import com.edgytech.swingfast.MenuItem;
  import com.edgytech.swingfast.Scroller;
  import com.edgytech.swingfast.TabbedDiv;
  import com.edgytech.swingfast.Tree;
  import com.edgytech.swingfast.XmlJComponentUnit;
  import com.mongodb.DBObject;
  import com.mongodb.MongoClient;
  import com.mongodb.MongoException;
  import java.awt.event.WindowEvent;
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileWriter;
  import java.io.IOException;
  import java.net.UnknownHostException;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.ConcurrentLinkedQueue;
  import java.util.jar.JarFile;
  import java.util.logging.Handler;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.swing.SwingUtilities;
  import org.xml.sax.SAXException;
  
  /**
   *
   * @author antoine
   */
  public class UMongo extends Application implements Runnable {
  
      enum Item {
  
          workspace,
          workspaceScroll,
          tree,
          treeScroll,
          mainMenu,
          mainToolBar,
          frame,
          globalStore,
          docViewDialog,
          docTree,
          tabbedResult,
          jobBar,
      }
      public final static UMongo instance = new UMongo();
      private ArrayList<MongoNode> mongos = new ArrayList<MongoNode>();
      boolean stopped = false;
      BaseTreeNode node = null;
      FileWriter activityLogWriter = null;
      boolean activityLogFirstResult = false;
      Handler applicationLogHandler = null;
      String pluginFolder = null;
      BinaryDecoder binaryDecoder = null;
  
      public UMongo() {
          super(true);
          setEnumBinding(Item.values(), null);
      }
  
      Frame getFrame() {
          return (Frame) getBoundUnit(Item.frame);
      }
  
      Workspace getWorkspace() {
          return (Workspace) getBoundUnit(Item.workspace);
      }
  
      Scroller getWorkspaceScroll() {
          return (Scroller) getBoundUnit(Item.workspaceScroll);
      }
  
      Tree getTree() {
          return (Tree) getBoundUnit(Item.tree);
      }
  
      MainMenu getMainMenu() {
          return (MainMenu) getBoundUnit(Item.mainMenu);
      }
  
      PreferencesDialog getPreferences() {
          return getMainMenu().getPreferences();
      }
  
      MainToolBar getMainToolBar() {
          return (MainToolBar) getBoundUnit(Item.mainToolBar);
      }
  
      public void load() throws IOException, SAXException {
          xmlLoad(Resource.getXmlDir(), Resource.File.umongo, null);
      }
  
      public void loadSettings() throws IOException, SAXException {
          try {
              xmlLoad(Resource.getConfDir(), Resource.File.umongo, null);
          } catch (FileNotFoundException e) {
              // means no custom setting
          }
      }
  
      public void saveSettings() {
          try {
              xmlSave(Resource.getConfDir(), Resource.File.umongo, null);
          } catch (Exception ex) {
              getLogger().log(Level.SEVERE, ex.getMessage(), ex);
          }
      }
  
      @Override
      public void initialize() {
          try {
              load();
              loadSettings();
          } catch (Exception ex) {
              getLogger().log(Level.SEVERE, null, ex);
          }
  
          Thread maintenance = new Thread(this);
          maintenance.setDaemon(true);
          maintenance.start();
      }
  
      @Override
      public void wrapUp() {
          saveSettings();
      }
  
      public void start() {
          getLogger().log(Level.INFO, "UMONGO STARTING");
      }
  
      public void stop() {
          getLogger().log(Level.INFO, "UMONGO STOPPING");
          stopped = true;
      }
  
      public static void main(String[] args) {
  //        LogManager.getLogManager().getLogger("").setLevel(Level.FINE);
  
          instance.launch();
      }
  
      GlobalStore getGlobalStore() {
          return (GlobalStore) getBoundUnit(Item.globalStore);
      }
  
      void addMongoClient(MongoClient mongo, List<String> dbs) throws MongoException, UnknownHostException {
          MongoNode node = new MongoNode(mongo, dbs);
          getTree().addChild(node);
          mongos.add(node);
          getTree().structureComponent();
          getTree().expandNode(node);
          getTree().selectNode(node);
      }
  
      void disconnect(MongoNode node) {
          mongos.remove(node);
  
          node.removeNode();
          MongoClient mongo = ((MongoNode) node).getMongoClient();
          mongo.close();
  
          if (mongos.size() > 0) {
              MongoNode other = mongos.get(0);
              getTree().expandNode(other);
              getTree().selectNode(other);
          } else {
              displayElement(null);
          }
  
      }
  
      public ArrayList<MongoNode> getMongos() {
          return mongos;
      }
  
      void displayElement(XmlJComponentUnit unit) {
          getWorkspace().setContent(unit);
      }
  
      void showError(String in, Exception ex) {
          ErrorDialog dia = getGlobalStore().getErrorDialog();
          dia.setException(ex, in);
          dia.show();
      }
  
      TabbedDiv getTabbedResult() {
          return (TabbedDiv) getBoundUnit(Item.tabbedResult);
      }
      
      void removeAllTabs() {
          TabbedDiv tabs = getTabbedResult();
          tabs.removeAllChildren();
          tabs.structureComponent();
      }
  
      JobBar getJobBar() {
          return (JobBar) getBoundUnit(Item.jobBar);
      }
  
      public void displayNode(BaseTreeNode node) {
          this.node = node;
          BasePanel panel = null;
          if (node instanceof MongoNode) {
              panel = getGlobalStore().getMongoPanel();
          } else if (node instanceof DbNode) {
              panel = getGlobalStore().getDbPanel();
          } else if (node instanceof CollectionNode) {
              panel = getGlobalStore().getCollectionPanel();
          } else if (node instanceof IndexNode) {
              panel = getGlobalStore().getIndexPanel();
          } else if (node instanceof ServerNode) {
              panel = getGlobalStore().getServerPanel();
          } else if (node instanceof RouterNode) {
              panel = getGlobalStore().getRouterPanel();
          } else if (node instanceof ReplSetNode) {
              panel = getGlobalStore().getReplSetPanel();
          }
  
          panel.setNode(node);
          // cant load checkpoint here, means all dialogs get reset
  //        panel.xmlLoadCheckpoint();
          displayElement(panel);
      }
  
      public BaseTreeNode getNode() {
          return node;
      }
  
      public void runJob(DbJob job) {
          getJobBar().addJob(job);
          job.start();
      }
  
      public void removeJob(DbJob job) {
          getJobBar().removeJob(job);
      }
      
      long _nextTreeUpdate = System.currentTimeMillis();
      ConcurrentLinkedQueue<BaseTreeNode> nodesToRefresh = new ConcurrentLinkedQueue<BaseTreeNode>();
  
      void addNodeToRefresh(BaseTreeNode node) {
          nodesToRefresh.add(node);
      }
  
      public void run() {
          while (!stopped) {
              try {
                  long now = System.currentTimeMillis();
                  int treeRate = getPreferences().getTreeUpdateRate();
                  if (treeRate > 0 && _nextTreeUpdate < now) {
                      _nextTreeUpdate = now + treeRate;
                      SwingUtilities.invokeAndWait(new Runnable() {
                          public void run() {
                              long start = System.currentTimeMillis();
                              // need structure here, to trigger refresh()
                              getTree().structureComponent();
                              getLogger().log(Level.FINE, "Tree update took " + (System.currentTimeMillis() - start));
  //                           getTree().structureComponent();
                          }
                      });
                  }
  
                  BaseTreeNode node = null;
                  while ((node = nodesToRefresh.poll()) != null) {
                      node.refresh();
                      final BaseTreeNode fnode = node;
                      SwingUtilities.invokeAndWait(new Runnable() {
                          public void run() {
                              fnode.updateComponent(false);
                          }
                      });
                  }
  
              } catch (Exception ex) {
                  getLogger().log(Level.SEVERE, null, ex);
              }
              try {
                  Thread.sleep(100);
              } catch (InterruptedException ex) {
                  getLogger().log(Level.SEVERE, null, ex);
              }
          }
      }
  
      @Override
      public void windowClosing(WindowEvent e) {
          if (getJobBar().hasChildren()) {
              String text = "There are jobs running, force exit?";
              ConfirmDialog confirm = new ConfirmDialog(null, "Confirm Exit", null, text);
              if (!confirm.show()) {
                  return;
              }
          }
          super.windowClosing(e);
      }
  
      void updateLogging() {
          synchronized (this) {
              try {
                  Handler handler = getPreferences().getApplicationLogHandler();
                  if (handler == null) {
                      if (applicationLogHandler != null) {
                          Logger.getLogger("").removeHandler(applicationLogHandler);
                      }
                  } else {
                      if (applicationLogHandler == null) {
                          Logger.getLogger("").addHandler(handler);
                      } else if (!applicationLogHandler.equals(handler)) {
                          Logger.getLogger("").removeHandler(applicationLogHandler);
                          Logger.getLogger("").addHandler(handler);
                      }
                  }
                  applicationLogHandler = handler;
  
                  String logFile = getPreferences().getActivityLogFile();
                  if (logFile != null) {
                      if (activityLogWriter != null) {
                          activityLogWriter.close();
                      }
                      activityLogWriter = new FileWriter(logFile, true);
                      activityLogFirstResult = getPreferences().getActivityLogFirstResult();
                  } else {
                      if (activityLogWriter != null) {
                          activityLogWriter.close();
                      }
                      activityLogWriter = null;
                  }
              } catch (IOException ex) {
                  getLogger().log(Level.WARNING, null, ex);
              }
          }
      }
  
      boolean isLoggingOn() {
          return activityLogWriter != null;
      }
  
      boolean isLoggingFirstResultOn() {
          return activityLogFirstResult;
      }
  
      void logActivity(DBObject obj) {
          synchronized (this) {
              try {
                  if ("Auth".equals(obj.get("name"))) {
                      // dont log auth
                      return;
                  }
  
                  activityLogWriter.write(MongoUtils.getJSON(obj));
                  activityLogWriter.write("\n");
                  activityLogWriter.flush();
              } catch (Exception ex) {
                  getLogger().log(Level.WARNING, null, ex);
              }
          }
      }
  
      void updatePlugins() {
          String folder = getPreferences().getPluginFolder();
          if (folder != null && folder.equals(pluginFolder)) {
              return;
          }
  
          pluginFolder = folder;
          resetPlugins();
          if (folder == null) {
              return;
          }
  
  //        System.setSecurityManager(new PluginSecurityManager(pluginFolder));
          
          //System.getProperty("user.dir")
          File dir = new File(pluginFolder);
          PluginClassLoader cl = new PluginClassLoader(dir);
          if (dir.exists() && dir.isDirectory()) {
              // we'll only load classes directly in this directory -
              // no subdirectories, and no classes in packages are recognized
              File[] files = dir.listFiles();
              for (int i = 0; i < files.length; i++) {
                  try {
                      List<Class> classes = null;
                      
                      File file = files[i];
                      if (file.getPath().endsWith(".jar")) {
                          getLogger().info("Attempting to load plugin " + file.getPath());
                          JarFile jar = new JarFile(file);
                          classes = cl.loadClasses(jar);
                      }
                      
  /*                    // only consider files ending in ".class"
                      if (!files[i].endsWith(".class")) {
                          continue;
                      }
  
                      getLogger().info("Attempting to load plugin " + files[i]);
                      Class c = cl.loadClass(files[i].substring(0, files[i].indexOf("."))); */
  
                      for (Class c : classes) {
                          Class[] intf = c.getInterfaces();
                          for (int j = 0; j < intf.length; j++) {
                              if (intf[j].getName().equals("com.edgytech.umongo.BinaryDecoder")) {
                                  getLogger().info("Detected BinaryDecoder plugin in " + c.getCanonicalName());
                                  // the following line assumes that PluginFunction has a no-argument constructor
                                  BinaryDecoder bd = (BinaryDecoder) c.newInstance();
                                  binaryDecoder = bd;
                              }
                          }
                      }
                  } catch (Exception ex) {
                      getLogger().log(Level.WARNING, null, ex);
                  }
              }
          }
  
      }
  
      void resetPlugins() {
          binaryDecoder = null;
      }
  
      BinaryDecoder getBinaryDecoder() {
          return binaryDecoder;
      }
  
      @Override
      public void handleMacAbout() {
          ((MenuItem)getMainMenu().getBoundUnit(MainMenu.Item.about)).getButton().doClick();
      }
  }
  