All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wvlet.airframe.jmx.JMXMBean.scala Maven / Gradle / Ivy

/*
 * 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 wvlet.airframe.jmx

import java.lang.annotation.Annotation
import java.lang.reflect.Method

import javax.management.*
import wvlet.log.LogSupport
import wvlet.airframe.surface.reflect.*
import wvlet.airframe.surface.{MethodSurface, Parameter, ParameterBase, Surface}

import wvlet.airframe.{jmx => aj}

/**
  * Expose object information using DynamicMBean
  */
case class JMXMBean(obj: AnyRef, mBeanInfo: MBeanInfo, attributes: Seq[MBeanParameter]) extends DynamicMBean {
  assert(obj != null)
  private lazy val attributeTable = attributes.map(a => a.name -> a).toMap

  override def getAttributes(attributes: Array[String]): AttributeList = {
    val l = new AttributeList(attributes.length)
    for (a <- attributes) {
      l.add(getAttribute(a))
    }
    l
  }
  override def getAttribute(attribute: String): AnyRef = {
    attributeTable.get(attribute) match {
      case Some(a) => a.get(obj)
      case None =>
        throw new AttributeNotFoundException(s"${attribute} is not found in ${obj.getClass.getName}")
    }
  }
  override def getMBeanInfo: MBeanInfo = mBeanInfo

  override def setAttributes(attributes: AttributeList): AttributeList = {
    val l = new AttributeList(attributes.size())
    import scala.jdk.CollectionConverters.*
    for (a <- attributes.asList().asScala) {
      l.add(setAttribute(a))
    }
    l
  }
  override def setAttribute(attribute: Attribute): Unit = {
    throw new AttributeNotFoundException(s"Setter for ${attribute.getName} is not found in ${obj.getClass.getName}")
//    attributeTable.get(attribute.getName) match {
//      case Some(a) => a.set(obj, attribute.getValue)
//      case None =>
//
//    }
  }
  override def invoke(actionName: String, params: Array[AnyRef], signature: Array[String]): AnyRef = {
    throw new UnsupportedOperationException("JMXMBean.invoke is not supported")
  }
}

object JMXMBean extends JMXMBeanCompat with LogSupport {
  private case class JMXMethod(m: MethodSurface, jmxAnnotation: JMX)

  def of[A](obj: A, surface: Surface, methodSurfaces: Seq[MethodSurface]): JMXMBean = {
    // Find JMX description
    val cl = obj.getClass

    val description = cl.getAnnotation(classOf[aj.JMX]) match {
      case a if a != null => a.description()
      case _              => ""
    }
    debug(s"Find JMX methods in ${cl}, description:${description}")

    // Collect JMX parameters from the class
    val mbeanParams = collectMBeanParameters(None, surface, methodSurfaces)
    val attrInfo = mbeanParams.map { x =>
      val desc = new ImmutableDescriptor()
      new MBeanAttributeInfo(
        x.name,
        x.valueType.rawType.getName,
        x.description,
        true,
        false,
        false
      )
    }

    val mbeanInfo = new MBeanInfo(
      cl.getName,
      description,
      attrInfo.toArray[MBeanAttributeInfo],
      Array.empty[MBeanConstructorInfo],
      Array.empty[MBeanOperationInfo],
      Array.empty[MBeanNotificationInfo]
    )

    new JMXMBean(obj.asInstanceOf[AnyRef], mbeanInfo, mbeanParams)
  }

  private def getJMXAnnotationInfo(h: ParameterBase): (String, String) = {
    (h match {
      case p: Parameter     => p.findAnnotationOf[aj.JMX]
      case m: MethodSurface => m.findAnnotationOf[aj.JMX]
    }).map { a =>
      (a.name, a.description())
    }.getOrElse("", "")
  }

  private def collectMBeanParameters(
      parent: Option[ParameterBase],
      surface: Surface,
      methods: Seq[MethodSurface]
  ): Seq[MBeanParameter] = {
    val jmxParams: Seq[ParameterBase] = surface.params.filter(_.findAnnotationOf[aj.JMX].isDefined) ++ methods.filter(
      _.findAnnotationOf[aj.JMX].isDefined
    )

    jmxParams.flatMap { p =>
      val (name, description) = getJMXAnnotationInfo(p)
      val attrName            = if (name.isEmpty) p.name else name
      val paramName           = parent.map(x => s"${x.name}.${attrName}").getOrElse(attrName)

      if (isNestedMBean(p)) {
        collectMBeanParameters(
          Some(p),
          p.surface,
          // In Scala 3, we cannot traverse nested parameters at ease without unless runtime reflection is available
          // So when using nested JMX, we can support only nested JMX parameters, but not nested methods.
          Seq.empty
        )
      } else {
        Seq(
          parent match {
            case Some(pt) =>
              NestedMBeanParameter(paramName, description, pt, p)
            case None =>
              MBeanObjectParameter(paramName, description, p)
          }
        )
      }
    }
  }

  private def isNestedMBean(p: ParameterBase): Boolean = {
    import wvlet.airframe.surface.reflect.*

    // We can support only nested parameters
    p.surface.params.exists(x => x.findAnnotationOf[aj.JMX].isDefined)
  }

  def collectUniqueAnnotations(m: Method): Seq[Annotation] = {
    collectUniqueAnnotations(m.getAnnotations)
  }

  def collectUniqueAnnotations(lst: Array[Annotation]): Seq[Annotation] = {
    var seen   = Set.empty[Annotation]
    val result = Seq.newBuilder[Annotation]

    def loop(lst: Array[Annotation]): Unit = {
      for (a <- lst) {
        if (!seen.contains(a)) {
          seen += a
          result += a
          loop(a.annotationType().getAnnotations)
        }
      }
    }
    loop(lst)
    result.result()
  }

//  def buildDescription(a:Annotation) {
//    for(m <- a.annotationType().getMethods.toSeq) {
//      m.getAnnotation(classOf[DescriptorKey]) match {
//        case descriptorKey if descriptorKey != null =>
//          val name = descriptorKey.value()
//          Try(m.invoke(a)).map { v =>
//
//          }
//
//      }
//
//    }
//  }
//
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy