<commit_before>/**
 * @module Core
 */

/// <reference path="../../includes.ts"/>
/// <reference path="jmxHelpers.ts"/>
/// <reference path="../../jvm/ts/jolokiaService.ts"/>
module Core {

  var log:Logging.Logger = Logger.get("workspace");
  export var tree:any = null;

  /**
   * @class NavMenuItem
   */
  export interface NavMenuItem {
    id: string;
    content: string;
    title?: string;
    isValid?: (workspace:Core.Workspace, perspectiveId?:string) => any;
    isActive?: (worksace:Core.Workspace) => boolean;
    href: () => any;
  }


  /**
   * @class Workspace
   */
  export class Workspace {
    public operationCounter = 0;
    public selection:NodeSelection;
    public tree:Folder = new Folder('MBeans');
    public mbeanTypesToDomain = {};
    public mbeanServicesToDomain = {};
    public attributeColumnDefs = {};
    public onClickRowHandlers = {};
    public treePostProcessors = {};
    public topLevelTabs:any = undefined 
    public subLevelTabs = [];
    public keyToNodeMap = {};
    public pluginRegisterHandle = null;
    public pluginUpdateCounter = null;
    public treeWatchRegisterHandle = null;
    public treeWatcherCounter = null;
    public treeElement = null;
    private treeFetched = false;
    // mapData allows to store arbitrary data on the workspace
    public mapData = {};

    private rootId = 'root';
    private separator = '-';

    constructor(public jolokia,
                public jolokiaStatus,
                public jmxTreeLazyLoadRegistry,
                public $location,
                public $compile:ng.ICompileService,
                public $templateCache:ng.ITemplateCacheService,
                public localStorage:WindowLocalStorage,
                public $rootScope,
                public userDetails,
                public HawtioNav:HawtioMainNav.Registry) {

      // set defaults
      if (!('autoRefresh' in localStorage)) {
        localStorage['autoRefresh'] = true;
      }
      if (!('updateRate' in localStorage)) {
        localStorage['updateRate'] = 5000;
      }
      var workspace = this;
      this.topLevelTabs = {
        push: (item:NavMenuItem) => {
          log.debug("Added menu item: ", item);
          var tab = {
            id: item.id,
            title: () => item.content,
            isValid: () => item.isValid(workspace),
            href: () => UrlHelpers.noHash(item.href()),
          }
          if (item.isActive) {
            tab['isSelected'] = () => item.isActive(workspace);
          }
          workspace.HawtioNav.add(tab);
        },
        find: (search:(NavMenuItem) => void) => {

        }
      };

    }

    /**
     * Creates a shallow copy child workspace with its own selection and location
     * @method createChildWorkspace
     * @param {ng.ILocationService} location
     * @return {Workspace}
     */
    public createChildWorkspace(location): Workspace {
      var child = new Workspace(this.jolokia, this.jolokiaStatus, this.jmxTreeLazyLoadRegistry,
              this.$location, this.$compile, this.$templateCache, this.localStorage, this.$rootScope, this.userDetails, this.HawtioNav);
      // lets copy across all the properties just in case
      angular.forEach(this, (value, key) => child[key] = value);
      child.$location = location;
      return child;
    }

    getLocalStorage(key:string) {
      return this.localStorage[key];
    }

    setLocalStorage(key:string, value:any) {
      this.localStorage[key] = value;
    }

    public loadTree() {
      var workspace = this;
      if (this.jolokia['isDummy']) {
        setTimeout(() => {
          workspace.treeFetched = true;
          workspace.populateTree({
            value: {}
          });
        }, 10);
        return;
      }
      if (Core.tree) {
        setTimeout(() => {
          workspace.treeFetched = true;
          workspace.populateTree(tree);
          Core.tree = null;
        }, 1);
      } else {
        var flags = {
          ignoreErrors: true, 
          error: (response) => {
            workspace.treeFetched = true;
            log.debug("Error fetching JMX tree: ", response);
          }
        };
        log.debug("jolokia: ", this.jolokia);
        this.jolokia.request({ 'type': 'list' }, Core.onSuccess((response) => {
          if (response.value) {
            this.jolokiaStatus.xhr = null;
          }
          workspace.treeFetched = true;
          workspace.populateTree(response);  
        }, flags));
      }
    }


    /**
     * Adds a post processor of the tree to swizzle the tree metadata after loading
     * such as correcting any typeName values or CSS styles by hand
     * @method addTreePostProcessor
     * @param {Function} processor
     */
    public addTreePostProcessor(processor:(tree:any) => void) {
      var numKeys = _.keys(this.treePostProcessors).length;
      var nextKey = numKeys + 1;
      return this.addNamedTreePostProcessor(nextKey + '', processor);
    }

    public addNamedTreePostProcessor(name:string, processor:(tree:any) => void) {
      this.treePostProcessors[name] = processor;
      var tree = this.tree;
      if (this.treeFetched && tree) {
        // the tree is loaded already so lets process it now :)
        processor(tree);
      }
      return name;
    }

    public removeNamedTreePostProcessor(name:string) {
      delete this.treePostProcessors[name];
    }

    public maybeMonitorPlugins() {
      if (this.treeContainsDomainAndProperties("hawtio", {type: "Registry"})) {
        if (this.pluginRegisterHandle === null) {
          this.pluginRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeUpdatePlugins), {
            type: "read",
            mbean: "hawtio:type=Registry",
            attribute: "UpdateCounter"
          });
        }
      } else {
        if (this.pluginRegisterHandle !== null) {
          this.jolokia.unregister(this.pluginRegisterHandle);
          this.pluginRegisterHandle = null;
          this.pluginUpdateCounter = null;
        }
      }

      // lets also listen to see if we have a JMX tree watcher
      if (this.treeContainsDomainAndProperties("hawtio", {type: "TreeWatcher"})) {
        if (this.treeWatchRegisterHandle === null) {
          this.treeWatchRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeReloadTree), {
            type: "read",
            mbean: "hawtio:type=TreeWatcher",
            attribute: "Counter"
          });
        }
      }
    }

    public maybeUpdatePlugins(response) {
      if (this.pluginUpdateCounter === null) {
        this.pluginUpdateCounter = response.value;
        return;
      }
      if (this.pluginUpdateCounter !== response.value) {
        if (Core.parseBooleanValue(localStorage['autoRefresh'])) {
          window.location.reload();
        }
      }
    }

    public maybeReloadTree(response) {
      var counter = response.value;
      if (this.treeWatcherCounter === null) {
        this.treeWatcherCounter = counter;
        return;
      }
      if (this.treeWatcherCounter !== counter) {
        this.treeWatcherCounter = counter;
        this.jolokia.list(null, onSuccess(this.wrapInValue, {ignoreErrors: true, maxDepth: 2}));
      }
    }

    private wrapInValue(response) {
      this.populateTree({value: response});
    }

    public folderGetOrElse(folder: Folder, value: string): Folder {
      if (folder) {
        try {
          return folder.getOrElse(value);
        } catch (e) {
          log.warn("Failed to find value " + value + " on folder " + folder);
        }
      }
      return null;
    }

    public populateTree(response): void {
      log.debug("JMX tree has been loaded, data: ", response.value);

      this.mbeanTypesToDomain = {};
      this.mbeanServicesToDomain = {};
      this.keyToNodeMap = {};

      var newTree = new Folder('MBeans');
      newTree.key = this.rootId;
      var domains = <Core.JMXDomains>response.value;
      angular.forEach(domains, (domain, domainName) => {
        this.populateDomainFolder(newTree, domainName, domain);
      });

      newTree.sortChildren(true);

      // now lets mark the nodes with no children as lazy loading...
      this.enableLazyLoading(newTree);
      this.tree = newTree;

      var processors = this.treePostProcessors;
      _.forIn(processors, (fn: (Folder) => void, key) => {
        log.debug("Running tree post processor: ", key);
        fn(newTree);
      });

      this.maybeMonitorPlugins();

      var rootScope = this.$rootScope;
      if (rootScope) {
        rootScope.$broadcast('jmxTreeUpdated');
        Core.$apply(rootScope);
      }
    }

    private initFolder(folder: Folder, domain: string, folderNames: string[]): void {
      folder.domain = domain;
      if (!folder.key) {
        folder.key = this.rootId + this.separator + folderNames.join(this.separator);
      }
      folder.folderNames = folderNames;
      log.debug("    folder: domain=" + folder.domain + ", key=" + folder.key);
    }

    private populateDomainFolder(tree: Folder, domainName: string, domain: Core.JMXDomain): void {
      log.debug("JMX tree domain: " + domainName);
      var domainClass = Core.escapeDots(domainName);
      var folder = this.folderGetOrElse(tree, domainName);
      this.initFolder(folder, domainName, [domainName]);
      angular.forEach(domain, (mbean, mbeanName) => {
        this.populateMBeanFolder(folder, domainClass, mbeanName, mbean);
      });
    }

    private populateMBeanFolder(domainFolder: Folder, domainClass: string, mbeanName: string, mbean: JMXMBean): void {
      log.debug("  JMX tree mbean: " + mbeanName);

      var entries = {};
      var paths = [];
      var typeName = null;
      var serviceName = null;
      mbeanName.split(',').forEach(prop => {
        // do not use split('=') as it splits wrong when there is a space in the mbean name
        // var kv = prop.split('=');
        var kv = this.splitMBeanProperty(prop);
        var key = kv[0];
        var value = kv[1] || key;
        entries[key] = value;
        var moveToFront = false;
        var lowerKey = key.toLowerCase();
        if (lowerKey === "type") {
          typeName = value;
          // if the type name value already exists in the root node
          // of the domain then lets move this property around too
          if (domainFolder.map[value]) {
            moveToFront = true;
          }
        }
        if (lowerKey === "service") {
          serviceName = value;
        }
        if (moveToFront) {
          paths.unshift(value);
        } else {
          paths.push(value);
        }
      });

      var folder = domainFolder;
      var domainName = domainFolder.domain;
      var folderNames = _.clone(domainFolder.folderNames);
      var lastPath = paths.pop();
      paths.forEach(path => {
        folder = this.folderGetOrElse(folder, path);
        if (folder) {
          folderNames.push(path);
          this.configureFolder(folder, domainName, domainClass, folderNames, path);
        }
      });

      if (folder) {
        folder = this.folderGetOrElse(folder, lastPath);
        if (folder) {
          // lets add the various data into the folder
          folder.entries = entries;<commit_msg>ENTESB-6571 - A-MQ7 hawt.io web console does not properly sanitize queue names before displaying them<commit_after>/**
 * @module Core
 */

/// <reference path="../../includes.ts"/>
/// <reference path="jmxHelpers.ts"/>
/// <reference path="../../jvm/ts/jolokiaService.ts"/>
module Core {

  var log:Logging.Logger = Logger.get("workspace");
  export var tree:any = null;

  /**
   * @class NavMenuItem
   */
  export interface NavMenuItem {
    id: string;
    content: string;
    title?: string;
    isValid?: (workspace:Core.Workspace, perspectiveId?:string) => any;
    isActive?: (worksace:Core.Workspace) => boolean;
    href: () => any;
  }


  /**
   * @class Workspace
   */
  export class Workspace {
    public operationCounter = 0;
    public selection:NodeSelection;
    public tree:Folder = new Folder('MBeans');
    public mbeanTypesToDomain = {};
    public mbeanServicesToDomain = {};
    public attributeColumnDefs = {};
    public onClickRowHandlers = {};
    public treePostProcessors = {};
    public topLevelTabs:any = undefined 
    public subLevelTabs = [];
    public keyToNodeMap = {};
    public pluginRegisterHandle = null;
    public pluginUpdateCounter = null;
    public treeWatchRegisterHandle = null;
    public treeWatcherCounter = null;
    public treeElement = null;
    private treeFetched = false;
    // mapData allows to store arbitrary data on the workspace
    public mapData = {};

    private rootId = 'root';
    private separator = '-';

    constructor(public jolokia,
                public jolokiaStatus,
                public jmxTreeLazyLoadRegistry,
                public $location,
                public $compile:ng.ICompileService,
                public $templateCache:ng.ITemplateCacheService,
                public localStorage:WindowLocalStorage,
                public $rootScope,
                public userDetails,
                public HawtioNav:HawtioMainNav.Registry) {

      // set defaults
      if (!('autoRefresh' in localStorage)) {
        localStorage['autoRefresh'] = true;
      }
      if (!('updateRate' in localStorage)) {
        localStorage['updateRate'] = 5000;
      }
      var workspace = this;
      this.topLevelTabs = {
        push: (item:NavMenuItem) => {
          log.debug("Added menu item: ", item);
          var tab = {
            id: item.id,
            title: () => item.content,
            isValid: () => item.isValid(workspace),
            href: () => UrlHelpers.noHash(item.href()),
          }
          if (item.isActive) {
            tab['isSelected'] = () => item.isActive(workspace);
          }
          workspace.HawtioNav.add(tab);
        },
        find: (search:(NavMenuItem) => void) => {

        }
      };

    }

    /**
     * Creates a shallow copy child workspace with its own selection and location
     * @method createChildWorkspace
     * @param {ng.ILocationService} location
     * @return {Workspace}
     */
    public createChildWorkspace(location): Workspace {
      var child = new Workspace(this.jolokia, this.jolokiaStatus, this.jmxTreeLazyLoadRegistry,
              this.$location, this.$compile, this.$templateCache, this.localStorage, this.$rootScope, this.userDetails, this.HawtioNav);
      // lets copy across all the properties just in case
      angular.forEach(this, (value, key) => child[key] = value);
      child.$location = location;
      return child;
    }

    getLocalStorage(key:string) {
      return this.localStorage[key];
    }

    setLocalStorage(key:string, value:any) {
      this.localStorage[key] = value;
    }

    public loadTree() {
      var workspace = this;
      if (this.jolokia['isDummy']) {
        setTimeout(() => {
          workspace.treeFetched = true;
          workspace.populateTree({
            value: {}
          });
        }, 10);
        return;
      }
      if (Core.tree) {
        setTimeout(() => {
          workspace.treeFetched = true;
          workspace.populateTree(tree);
          Core.tree = null;
        }, 1);
      } else {
        var flags = {
          ignoreErrors: true, 
          error: (response) => {
            workspace.treeFetched = true;
            log.debug("Error fetching JMX tree: ", response);
          }
        };
        log.debug("jolokia: ", this.jolokia);
        this.jolokia.request({ 'type': 'list' }, Core.onSuccess((response) => {
          if (response.value) {
            this.jolokiaStatus.xhr = null;
          }
          workspace.treeFetched = true;
          workspace.populateTree(response);  
        }, flags));
      }
    }


    /**
     * Adds a post processor of the tree to swizzle the tree metadata after loading
     * such as correcting any typeName values or CSS styles by hand
     * @method addTreePostProcessor
     * @param {Function} processor
     */
    public addTreePostProcessor(processor:(tree:any) => void) {
      var numKeys = _.keys(this.treePostProcessors).length;
      var nextKey = numKeys + 1;
      return this.addNamedTreePostProcessor(nextKey + '', processor);
    }

    public addNamedTreePostProcessor(name:string, processor:(tree:any) => void) {
      this.treePostProcessors[name] = processor;
      var tree = this.tree;
      if (this.treeFetched && tree) {
        // the tree is loaded already so lets process it now :)
        processor(tree);
      }
      return name;
    }

    public removeNamedTreePostProcessor(name:string) {
      delete this.treePostProcessors[name];
    }

    public maybeMonitorPlugins() {
      if (this.treeContainsDomainAndProperties("hawtio", {type: "Registry"})) {
        if (this.pluginRegisterHandle === null) {
          this.pluginRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeUpdatePlugins), {
            type: "read",
            mbean: "hawtio:type=Registry",
            attribute: "UpdateCounter"
          });
        }
      } else {
        if (this.pluginRegisterHandle !== null) {
          this.jolokia.unregister(this.pluginRegisterHandle);
          this.pluginRegisterHandle = null;
          this.pluginUpdateCounter = null;
        }
      }

      // lets also listen to see if we have a JMX tree watcher
      if (this.treeContainsDomainAndProperties("hawtio", {type: "TreeWatcher"})) {
        if (this.treeWatchRegisterHandle === null) {
          this.treeWatchRegisterHandle = this.jolokia.register(angular.bind(this, this.maybeReloadTree), {
            type: "read",
            mbean: "hawtio:type=TreeWatcher",
            attribute: "Counter"
          });
        }
      }
    }

    public maybeUpdatePlugins(response) {
      if (this.pluginUpdateCounter === null) {
        this.pluginUpdateCounter = response.value;
        return;
      }
      if (this.pluginUpdateCounter !== response.value) {
        if (Core.parseBooleanValue(localStorage['autoRefresh'])) {
          window.location.reload();
        }
      }
    }

    public maybeReloadTree(response) {
      var counter = response.value;
      if (this.treeWatcherCounter === null) {
        this.treeWatcherCounter = counter;
        return;
      }
      if (this.treeWatcherCounter !== counter) {
        this.treeWatcherCounter = counter;
        this.jolokia.list(null, onSuccess(this.wrapInValue, {ignoreErrors: true, maxDepth: 2}));
      }
    }

    private wrapInValue(response) {
      this.populateTree({value: response});
    }

    public folderGetOrElse(folder: Folder, name: string): Folder {
      if (folder) {
        try {
          return folder.getOrElse(name);
        } catch (e) {
          log.warn("Failed to find name " + name + " on folder " + folder);
        }
      }
      return null;
    }

    public populateTree(response): void {
      log.debug("JMX tree has been loaded, data: ", response.value);

      this.mbeanTypesToDomain = {};
      this.mbeanServicesToDomain = {};
      this.keyToNodeMap = {};

      var newTree = new Folder('MBeans');
      newTree.key = this.rootId;
      var domains = <Core.JMXDomains>response.value;
      angular.forEach(domains, (domain, domainName) => {
        // domain name is displayed in the tree, so let's escape it here
        this.populateDomainFolder(newTree, _.escape(domainName), domain);
      });

      newTree.sortChildren(true);

      // now lets mark the nodes with no children as lazy loading...
      this.enableLazyLoading(newTree);
      this.tree = newTree;

      var processors = this.treePostProcessors;
      _.forIn(processors, (fn: (Folder) => void, key) => {
        log.debug("Running tree post processor: ", key);
        fn(newTree);
      });

      this.maybeMonitorPlugins();

      var rootScope = this.$rootScope;
      if (rootScope) {
        rootScope.$broadcast('jmxTreeUpdated');
        Core.$apply(rootScope);
      }
    }

    private initFolder(folder: Folder, domain: string, folderNames: string[]): void {
      folder.domain = domain;
      if (!folder.key) {
        folder.key = this.rootId + this.separator + folderNames.join(this.separator);
      }
      folder.folderNames = folderNames;
      log.debug("    folder: domain=" + folder.domain + ", key=" + folder.key);
    }

    private populateDomainFolder(tree: Folder, domainName: string, domain: Core.JMXDomain): void {
      log.debug("JMX tree domain: " + domainName);
      var domainClass = Core.escapeDots(domainName);
      var folder = this.folderGetOrElse(tree, domainName);
      this.initFolder(folder, domainName, [domainName]);
      angular.forEach(domain, (mbean, mbeanName) => {
        this.populateMBeanFolder(folder, domainClass, mbeanName, mbean);
      });
    }

    private populateMBeanFolder(domainFolder: Folder, domainClass: string, mbeanName: string, mbean: JMXMBean): void {
      log.debug("  JMX tree mbean: " + mbeanName);

      var entries = {};
      var paths = [];
      var typeName = null;
      var serviceName = null;
      mbeanName.split(',').forEach(prop => {
        // do not use split('=') as it splits wrong when there is a space in the mbean name
        // var kv = prop.split('=');
        var kv = this.splitMBeanProperty(prop);
        var propKey = kv[0];
        // mbean property value is displayed in the tree, so let's escape it here
        var propValue = _.escape(kv[1] || propKey);
        entries[propKey] = propValue;
        var moveToFront = false;
        var lowerKey = propKey.toLowerCase();
        if (lowerKey === "type") {
          typeName = propValue;
          // if the type name value already exists in the root node
          // of the domain then lets move this property around too
          if (domainFolder.map[propValue]) {
            moveToFront = true;
          }
        }
        if (lowerKey === "service") {
          serviceName = propValue;
        }
        if (moveToFront) {
          paths.unshift(propValue);
        } else {
          paths.push(propValue);
        }
      });

      var folder = domainFolder;
      var domainName = domainFolder.domain;
      var folderNames = _.clone(domainFolder.folderNames);
      var lastPath = paths.pop();
      paths.forEach(path => {
        folder = this.folderGetOrElse(folder, path);
        if (folder) {
          folderNames.push(path);
          this.configureFolder(folder, domainName, domainClass, folderNames, path);
        }
      });

      if (folder) {
        folder = this.folderGetOrElse(folder, lastPath);
        if (folder) {
          // lets add the various data into the folder
          folder.entries = entries;
{'fraction_non_alphanumeric': 0.11970995385629532, 'fraction_numerical': 0.0012744451768842012, 'mean_word_length': 2.243443557582668, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '745887', 'n_tokens_mistral': 6534, 'n_tokens_neox': 6390, 'n_words': 1495}