/
mbean.go
244 lines (192 loc) · 8.04 KB
/
mbean.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
package mbean
import (
"errors"
"fmt"
"sync"
"github.com/arinn1204/gmx/pkg/extensions"
"tekao.net/jnigi"
)
// a collection of JNI representations of java primitive types
// these will be the boxed representations, not the true primitive
const (
STRING = "java/lang/String"
OBJECT = "java/lang/Object"
FLOAT = "java/lang/Float"
)
// Client is the overarching type that will facilitate JMX connections
// JmxConnection is the living connection that was created when `CreateMBeanConnection` was called to create the Client
// Env is the environment that belongs to this bean, this will not always match the JVM env!
type Client struct {
JmxURI string
Env *jnigi.Env
ClassHandlers sync.Map
InterfaceHandlers sync.Map
}
// Operation is the operation that is being performed
// Domain is the fully qualified name of the MBean `org.example`
// Name is the name of the mbean itself `game`
// Operation is the name of the operation that is attempted to be interacted with `getString`
// Args are the optional argument array that is for the operation
type Operation struct {
Domain string
Name string
Operation string
Args []OperationArgs
}
// OperationArgs is the type that holds data about the arguments used for MBean operations
// Value is the value that is being entered in string form
// JavaType is the fully qualified java type `java.lang.String`
// JavaContainerType is the fully qualified type of the container that will be holding JavaType
type OperationArgs struct {
Value string
JavaType string
JavaContainerType string
}
// BeanExecutor is the interface used around this package.
// This is how an execution is performed.
// This will always rely on the MBean Client's environment
// Close will handle any types of cleanup that is related directly to MBean operations
//
// for example: cleaning up the JMX connection and deleting the reference
type BeanExecutor interface {
RegisterClassHandler(typeName string, handler extensions.IHandler) error
RegisterInterfaceHandler(typeName string, handler extensions.InterfaceHandler) error
Execute(operation Operation) (string, error)
Get(domainName string, beanName string, attributeName string, args OperationArgs) (string, error)
Put(domainName string, beanName string, attributeName string, args OperationArgs) (string, error)
WithEnvironment(env *jnigi.Env) BeanExecutor
GetEnv() *jnigi.Env
OpenConnection(jndiURI string) (*jnigi.ObjectRef, error)
}
// RegisterClassHandler will register the given class handlers
// For a class handler to be valid it must implement a form of IClassHandler
func (mbean *Client) RegisterClassHandler(typeName string, handler extensions.IHandler) error {
mbean.ClassHandlers.Store(typeName, handler)
return nil
}
// RegisterInterfaceHandler will register the given class handlers
// For a class handler to be valid it must implement a form of IClassHandler
func (mbean *Client) RegisterInterfaceHandler(typeName string, handler extensions.InterfaceHandler) error {
mbean.InterfaceHandlers.Store(typeName, handler)
return nil
}
// WithEnvironment allos the client to spin up a new client using a new environment
// This is handy when using the same JmxConnection in sub threads
func (mbean *Client) WithEnvironment(env *jnigi.Env) BeanExecutor {
return &Client{
JmxURI: mbean.JmxURI,
Env: env,
ClassHandlers: mbean.ClassHandlers,
InterfaceHandlers: mbean.InterfaceHandlers,
}
}
// GetEnv will expose the underlying environment that the client is associated with
func (mbean *Client) GetEnv() *jnigi.Env {
return mbean.Env
}
// Close is a method that will clean up all of the MBeans resources
// It will close the JMX method within the JVM as well as deleting the connection
// from the JNI resources
func Close(env *jnigi.Env, connection *jnigi.ObjectRef) {
defer env.DeleteLocalRef(connection)
connection.CallMethod(env, "close", nil)
}
// OpenConnection is a method that will establish a new connection against
// the given URI
func (mbean *Client) OpenConnection(jndiURI string) (*jnigi.ObjectRef, error) {
stringRef, err := mbean.Env.NewObject("java/lang/String", []byte(jndiURI))
if err != nil {
return nil, fmt.Errorf("failed to create a string from %s::%s", jndiURI, err.Error())
}
jmxURL, err := mbean.Env.NewObject("javax/management/remote/JMXServiceURL", stringRef)
if err != nil {
return nil, errors.New("failed to create JMXServiceURL::" + err.Error())
}
if err != nil {
return nil, errors.New("failed to create a blank map::" + err.Error())
}
jmxConnector := jnigi.NewObjectRef("javax/management/remote/JMXConnector")
connectorFactory := "javax/management/remote/JMXConnectorFactory"
if err = mbean.Env.CallStaticMethod(connectorFactory, "connect", jmxConnector, jmxURL); err != nil {
return nil, errors.New("failed to create a JMX connection Factory::" + err.Error())
}
return jmxConnector, nil
}
// Execute is the orchestration for a JMX command execution.
func (mbean *Client) Execute(operation Operation) (string, error) {
returnString := jnigi.NewObjectRef(OBJECT)
if err := mbean.invoke(mbean.Env, operation, returnString); err != nil {
return "", err
}
return mbean.toGoString(mbean.Env, returnString)
}
func (mbean *Client) invoke(env *jnigi.Env, operation Operation, outParam *jnigi.ObjectRef) error {
objectName, err := getObjectName(env, operation)
if err != nil {
return errors.New("failed to create ObjectName::" + err.Error())
}
defer env.DeleteLocalRef(objectName)
connection, err := mbean.OpenConnection(mbean.JmxURI)
if err != nil {
return err
}
defer Close(mbean.Env, connection)
mBeanServerConnector, err := createMBeanServerConnection(env, connection)
if err != nil {
return errors.New("failed to create the mbean server connection::" + err.Error())
}
defer env.DeleteLocalRef(mBeanServerConnector)
typeReferences, types, err := getOperationParameterTypes(env, objectName, mBeanServerConnector, operation.Operation)
if err != nil {
return err
}
defer env.DeleteLocalRef(typeReferences)
names, err := mbean.getArray(env, operation.Args, types, OBJECT)
if names != nil {
defer env.DeleteLocalRef(names)
}
if err != nil {
return err
}
operationRef, err := stringHandler.ToJniRepresentation(env, operation.Operation)
defer env.DeleteLocalRef(operationRef)
if err != nil {
return err
}
if err = mBeanServerConnector.CallMethod(env, "invoke", outParam, objectName, operationRef, names, typeReferences); err != nil {
return errors.New("failed to call invoke::" + err.Error())
}
return nil
}
func (mbean *Client) getArray(env *jnigi.Env, args []OperationArgs, methodTypes []string, className string) (*jnigi.ObjectRef, error) {
types := make([]*jnigi.ObjectRef, 0)
for i, arg := range args {
obj, err := toJni(mbean, methodTypes[i], arg.JavaType, arg.JavaContainerType, arg.Value)
if err != nil {
return nil, err
}
types = append(types, obj)
}
return env.ToObjectArray(types, className), nil
}
func createMBeanServerConnection(env *jnigi.Env, connection *jnigi.ObjectRef) (*jnigi.ObjectRef, error) {
mBeanServerConnector := jnigi.NewObjectRef("javax/management/MBeanServerConnection")
if err := connection.CallMethod(env, "getMBeanServerConnection", mBeanServerConnector); err != nil {
return nil, errors.New("failed to create the mbean server connection::" + err.Error())
}
return mBeanServerConnector, nil
}
func toJni(mbean *Client, methodType string, javaType string, containerType string, value string) (*jnigi.ObjectRef, error) {
// the containers are always assumed to be interfaces
if interfaceHandler, exists := mbean.InterfaceHandlers.Load(methodType); exists && containerType != "" {
return interfaceHandler.(extensions.InterfaceHandler).ToJniRepresentation(mbean.Env, javaType, value)
} else if handler, exists := mbean.ClassHandlers.Load(methodType); exists {
var parsedVal any
parsedVal, err := toTypeFromString(value, methodType)
if err != nil {
return nil, err
}
return handler.(extensions.IHandler).ToJniRepresentation(mbean.Env, parsedVal)
}
return nil, fmt.Errorf("no handlers exist for %s", javaType)
}