;; Conversions between JMX data structures and idiomatic Clojure
;; docs in clojure/contrib/jmx.clj!!

;; by Stuart Halloway

;; Copyright (c) Stuart Halloway, 2009. All rights reserved.  The use
;; and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution.  By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license.  You must not
;; remove this notice, or any other, from this software.


(in-ns 'clojure.contrib.jmx)

(declare jmx->clj)

(defn jmx-url
  "Build a JMX URL from options."
  ([] (jmx-url {}))
  ([overrides]
     (let [opts (merge {:host "localhost", :port "3000"} overrides)]
       (format "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi" (opts :host) (opts :port)))))

(defmulti as-object-name
  "Interpret an object as a JMX ObjectName."
  { :arglists '([string-or-name]) }
  class)
(defmethod as-object-name String [n] (ObjectName. n))
(defmethod as-object-name ObjectName [n] n)

(defn composite-data->map [cd]
  (into {}
        (map (fn [attr] [(keyword attr) (jmx->clj (.get cd attr))])
             (.. cd getCompositeType keySet))))

(defn maybe-keywordize
  "Convert a string key to a keyword, leaving other types alone. Used to
   simplify keys in the tabular data API."
  [s]
  (if (string? s) (keyword s) s))

(defn maybe-atomize
  "Convert a list of length 1 into its contents, leaving other things alone.
  Used to simplify keys in the tabular data API."
  [k]
  (if (and (instance? java.util.List k)
           (= 1 (count k)))
    (first k)
    k))

(defvar simplify-tabular-data-key
  (comp maybe-keywordize maybe-atomize))

(defn tabular-data->map [td]
  (into {}
        ; the need for into-array here was a surprise, and may not
        ; work for all examples. Are keys always arrays?
        (map (fn [k]
               [(simplify-tabular-data-key k) (jmx->clj (.get td (into-array k)))])
             (.keySet td))))

(defmulti jmx->clj
  "Coerce JMX data structures into Clojure data.
  Handles CompositeData, TabularData, maps, and atoms."
  { :argslists '([jmx-data-structure]) }
  (fn [x]
    (cond
     (instance? javax.management.openmbean.CompositeData x) :composite
     (instance? javax.management.openmbean.TabularData x) :tabular
     (instance? clojure.lang.Associative x) :map
     :default :default)))
(defmethod jmx->clj :composite [c] (composite-data->map c))
(defmethod jmx->clj :tabular [t] (tabular-data->map t))
(defmethod jmx->clj :map [m]  (into {} (zipmap (keys m) (map jmx->clj (vals m)))))
(defmethod jmx->clj :default [obj] obj)

(def guess-attribute-map
     {"java.lang.Integer" "int"
      "java.lang.Boolean" "boolean"
      "java.lang.Long" "long"
      })

(defn guess-attribute-typename
  "Guess the attribute typename for MBeanAttributeInfo based on the attribute value."
  [value]
  (let [classname (.getName (class value))]
    (get guess-attribute-map classname classname)))

(defn build-attribute-info
  "Construct an MBeanAttributeInfo. Normally called with a key/value pair from a Clojure map."
  ([attr-name attr-value]
     (build-attribute-info
      (as-str attr-name)
      (guess-attribute-typename attr-value)
      (as-str attr-name) true false false))
  ([name type desc readable? writable? is?] (MBeanAttributeInfo. name type desc readable? writable? is? )))

(defn map->attribute-infos
  "Construct an MBeanAttributeInfo[] from a Clojure associative."
  [attr-map]
  (into-array (map (fn [[attr-name value]] (build-attribute-info attr-name value))
                   attr-map)))
