1/*
2 * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.management.internal;
27
28import com.sun.management.DiagnosticCommandMBean;
29import java.lang.reflect.Constructor;
30import java.lang.reflect.InvocationTargetException;
31import java.security.Permission;
32import java.util.*;
33import javax.management.Attribute;
34import javax.management.AttributeList;
35import javax.management.AttributeNotFoundException;
36import javax.management.Descriptor;
37import javax.management.ImmutableDescriptor;
38import javax.management.InvalidAttributeValueException;
39import javax.management.ListenerNotFoundException;
40import javax.management.MBeanException;
41import javax.management.MBeanInfo;
42import javax.management.MBeanNotificationInfo;
43import javax.management.MBeanOperationInfo;
44import javax.management.MBeanParameterInfo;
45import javax.management.MalformedObjectNameException;
46import javax.management.Notification;
47import javax.management.NotificationFilter;
48import javax.management.NotificationListener;
49import javax.management.ObjectName;
50import javax.management.ReflectionException;
51import sun.management.ManagementFactoryHelper;
52import sun.management.NotificationEmitterSupport;
53import sun.management.VMManagement;
54
55/**
56 * Implementation class for the diagnostic commands subsystem.
57 *
58 * @since 1.8
59 */
60public class DiagnosticCommandImpl extends NotificationEmitterSupport
61    implements DiagnosticCommandMBean {
62
63    private final VMManagement jvm;
64    private volatile Map<String, Wrapper> wrappers = null;
65    private static final String strClassName = "".getClass().getName();
66    private static final String strArrayClassName = String[].class.getName();
67    private final boolean isSupported;
68    private static DiagnosticCommandImpl diagCommandMBean = null;
69
70    static synchronized DiagnosticCommandMBean getDiagnosticCommandMBean() {
71        VMManagement jvm = ManagementFactoryHelper.getVMManagement();
72
73        // Remote Diagnostic Commands may not be supported
74        if (diagCommandMBean == null && jvm.isRemoteDiagnosticCommandsSupported()) {
75            diagCommandMBean = new DiagnosticCommandImpl(jvm);
76        }
77        return diagCommandMBean;
78    }
79
80    @Override
81    public Object getAttribute(String attribute) throws AttributeNotFoundException,
82        MBeanException, ReflectionException {
83        throw new AttributeNotFoundException(attribute);
84    }
85
86    @Override
87    public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
88        InvalidAttributeValueException, MBeanException, ReflectionException {
89        throw new AttributeNotFoundException(attribute.getName());
90    }
91
92    @Override
93    public AttributeList getAttributes(String[] attributes) {
94        return new AttributeList();
95    }
96
97    @Override
98    public AttributeList setAttributes(AttributeList attributes) {
99        return new AttributeList();
100    }
101
102    private class Wrapper {
103
104        String name;
105        String cmd;
106        DiagnosticCommandInfo info;
107        Permission permission;
108
109        Wrapper(String name, String cmd, DiagnosticCommandInfo info)
110                throws InstantiationException {
111            this.name = name;
112            this.cmd = cmd;
113            this.info = info;
114            this.permission = null;
115            Exception cause = null;
116            if (info.getPermissionClass() != null) {
117                try {
118                    Class<?> c = Class.forName(info.getPermissionClass());
119                    if (info.getPermissionAction() == null) {
120                        try {
121                            Constructor<?> constructor = c.getConstructor(String.class);
122                            permission = (Permission) constructor.newInstance(info.getPermissionName());
123
124                        } catch (InstantiationException | IllegalAccessException
125                                | IllegalArgumentException | InvocationTargetException
126                                | NoSuchMethodException | SecurityException ex) {
127                            cause = ex;
128                        }
129                    }
130                    if (permission == null) {
131                        try {
132                            Constructor<?> constructor = c.getConstructor(String.class, String.class);
133                            permission = (Permission) constructor.newInstance(
134                                    info.getPermissionName(),
135                                    info.getPermissionAction());
136                        } catch (InstantiationException | IllegalAccessException
137                                | IllegalArgumentException | InvocationTargetException
138                                | NoSuchMethodException | SecurityException ex) {
139                            cause = ex;
140                        }
141                    }
142                } catch (ClassNotFoundException ex) { }
143                if (permission == null) {
144                    InstantiationException iex =
145                            new InstantiationException("Unable to instantiate required permission");
146                    iex.initCause(cause);
147                }
148            }
149        }
150
151        public String execute(String[] args) {
152            if (permission != null) {
153                SecurityManager sm = System.getSecurityManager();
154                if (sm != null) {
155                    sm.checkPermission(permission);
156                }
157            }
158            if(args == null) {
159                return executeDiagnosticCommand(cmd);
160            } else {
161                StringBuilder sb = new StringBuilder();
162                sb.append(cmd);
163                for(int i=0; i<args.length; i++) {
164                    if(args[i] == null) {
165                        throw new IllegalArgumentException("Invalid null argument");
166                    }
167                    sb.append(" ");
168                    sb.append(args[i]);
169                }
170                return executeDiagnosticCommand(sb.toString());
171            }
172        }
173    }
174
175    DiagnosticCommandImpl(VMManagement jvm) {
176        this.jvm = jvm;
177        isSupported = jvm.isRemoteDiagnosticCommandsSupported();
178    }
179
180    private static class OperationInfoComparator implements Comparator<MBeanOperationInfo> {
181        @Override
182        public int compare(MBeanOperationInfo o1, MBeanOperationInfo o2) {
183            return o1.getName().compareTo(o2.getName());
184        }
185    }
186
187    @Override
188    public MBeanInfo getMBeanInfo() {
189        SortedSet<MBeanOperationInfo> operations = new TreeSet<>(new OperationInfoComparator());
190        Map<String, Wrapper> wrappersmap;
191        if (!isSupported) {
192            wrappersmap = Collections.emptyMap();
193        } else {
194            try {
195                String[] command = getDiagnosticCommands();
196                DiagnosticCommandInfo[] info = getDiagnosticCommandInfo(command);
197                MBeanParameterInfo stringArgInfo[] = new MBeanParameterInfo[]{
198                    new MBeanParameterInfo("arguments", strArrayClassName,
199                    "Array of Diagnostic Commands Arguments and Options")
200                };
201                wrappersmap = new HashMap<>();
202                for (int i = 0; i < command.length; i++) {
203                    String name = transform(command[i]);
204                    try {
205                        Wrapper w = new Wrapper(name, command[i], info[i]);
206                        wrappersmap.put(name, w);
207                        operations.add(new MBeanOperationInfo(
208                                w.name,
209                                w.info.getDescription(),
210                                (w.info.getArgumentsInfo() == null
211                                    || w.info.getArgumentsInfo().isEmpty())
212                                    ? null : stringArgInfo,
213                                strClassName,
214                                MBeanOperationInfo.ACTION_INFO,
215                                commandDescriptor(w)));
216                    } catch (InstantiationException ex) {
217                        // If for some reasons the creation of a diagnostic command
218                        // wrappers fails, the diagnostic command is just ignored
219                        // and won't appear in the DynamicMBean
220                    }
221                }
222            } catch (IllegalArgumentException | UnsupportedOperationException e) {
223                wrappersmap = Collections.emptyMap();
224            }
225        }
226        wrappers =  Collections.unmodifiableMap(wrappersmap);
227        HashMap<String, Object> map = new HashMap<>();
228        map.put("immutableInfo", "false");
229        map.put("interfaceClassName","com.sun.management.DiagnosticCommandMBean");
230        map.put("mxbean", "false");
231        Descriptor desc = new ImmutableDescriptor(map);
232        return new MBeanInfo(
233                this.getClass().getName(),
234                "Diagnostic Commands",
235                null, // attributes
236                null, // constructors
237                operations.toArray(new MBeanOperationInfo[operations.size()]), // operations
238                getNotificationInfo(), // notifications
239                desc);
240    }
241
242    @Override
243    public Object invoke(String actionName, Object[] params, String[] signature)
244            throws MBeanException, ReflectionException {
245        if (!isSupported) {
246            throw new UnsupportedOperationException();
247        }
248        if (wrappers == null) {
249            getMBeanInfo();
250        }
251        Wrapper w = wrappers.get(actionName);
252        if (w != null) {
253            if (w.info.getArgumentsInfo().isEmpty()
254                    && (params == null || params.length == 0)
255                    && (signature == null || signature.length == 0)) {
256                return w.execute(null);
257            } else if((params != null && params.length == 1)
258                    && (signature != null && signature.length == 1
259                    && signature[0] != null
260                    && signature[0].compareTo(strArrayClassName) == 0)) {
261                return w.execute((String[]) params[0]);
262            } else {
263                throw new ReflectionException(
264                    new NoSuchMethodException(actionName
265                    + ": mismatched signature "
266                    + (signature != null ? Arrays.toString(signature) : "[]")
267                    + " or parameters"));
268            }
269        } else {
270            throw new ReflectionException(
271                new NoSuchMethodException("Method " + actionName
272                + " with signature "
273                + (signature != null ? Arrays.toString(signature) : "[]")
274                + " not found"));
275        }
276    }
277
278    private static String transform(String name) {
279        StringBuilder sb = new StringBuilder();
280        boolean toLower = true;
281        boolean toUpper = false;
282        for (int i = 0; i < name.length(); i++) {
283            char c = name.charAt(i);
284            if (c == '.' || c == '_') {
285                toLower = false;
286                toUpper = true;
287            } else {
288                if (toUpper) {
289                    toUpper = false;
290                    sb.append(Character.toUpperCase(c));
291                } else if(toLower) {
292                    sb.append(Character.toLowerCase(c));
293                } else {
294                    sb.append(c);
295                }
296            }
297        }
298        return sb.toString();
299    }
300
301    private Descriptor commandDescriptor(Wrapper w) throws IllegalArgumentException {
302        HashMap<String, Object> map = new HashMap<>();
303        map.put("dcmd.name", w.info.getName());
304        map.put("dcmd.description", w.info.getDescription());
305        map.put("dcmd.vmImpact", w.info.getImpact());
306        map.put("dcmd.permissionClass", w.info.getPermissionClass());
307        map.put("dcmd.permissionName", w.info.getPermissionName());
308        map.put("dcmd.permissionAction", w.info.getPermissionAction());
309        map.put("dcmd.enabled", w.info.isEnabled());
310        StringBuilder sb = new StringBuilder();
311        sb.append("help ");
312        sb.append(w.info.getName());
313        map.put("dcmd.help", executeDiagnosticCommand(sb.toString()));
314        if (w.info.getArgumentsInfo() != null && !w.info.getArgumentsInfo().isEmpty()) {
315            HashMap<String, Object> allargmap = new HashMap<>();
316            for (DiagnosticCommandArgumentInfo arginfo : w.info.getArgumentsInfo()) {
317                HashMap<String, Object> argmap = new HashMap<>();
318                argmap.put("dcmd.arg.name", arginfo.getName());
319                argmap.put("dcmd.arg.type", arginfo.getType());
320                argmap.put("dcmd.arg.description", arginfo.getDescription());
321                argmap.put("dcmd.arg.isMandatory", arginfo.isMandatory());
322                argmap.put("dcmd.arg.isMultiple", arginfo.isMultiple());
323                boolean isOption = arginfo.isOption();
324                argmap.put("dcmd.arg.isOption", isOption);
325                if(!isOption) {
326                    argmap.put("dcmd.arg.position", arginfo.getPosition());
327                } else {
328                    argmap.put("dcmd.arg.position", -1);
329                }
330                allargmap.put(arginfo.getName(), new ImmutableDescriptor(argmap));
331            }
332            map.put("dcmd.arguments", new ImmutableDescriptor(allargmap));
333        }
334        return new ImmutableDescriptor(map);
335    }
336
337    private final static String notifName =
338        "javax.management.Notification";
339
340    private final static String[] diagFramNotifTypes = {
341        "jmx.mbean.info.changed"
342    };
343
344    private MBeanNotificationInfo[] notifInfo = null;
345
346    @Override
347    public MBeanNotificationInfo[] getNotificationInfo() {
348        synchronized (this) {
349            if (notifInfo == null) {
350                 notifInfo = new MBeanNotificationInfo[1];
351                 notifInfo[0] =
352                         new MBeanNotificationInfo(diagFramNotifTypes,
353                                                   notifName,
354                                                   "Diagnostic Framework Notification");
355            }
356        }
357        return notifInfo.clone();
358    }
359
360    private static long seqNumber = 0;
361    private static long getNextSeqNumber() {
362        return ++seqNumber;
363    }
364
365    private void createDiagnosticFrameworkNotification() {
366
367        if (!hasListeners()) {
368            return;
369        }
370        ObjectName on = null;
371        try {
372            on = ObjectName.getInstance(PlatformMBeanProviderImpl.DIAGNOSTIC_COMMAND_MBEAN_NAME);
373        } catch (MalformedObjectNameException e) { }
374        Notification notif = new Notification("jmx.mbean.info.changed",
375                                              on,
376                                              getNextSeqNumber());
377        notif.setUserData(getMBeanInfo());
378        sendNotification(notif);
379    }
380
381    @Override
382    public synchronized void addNotificationListener(NotificationListener listener,
383            NotificationFilter filter,
384            Object handback) {
385        boolean before = hasListeners();
386        super.addNotificationListener(listener, filter, handback);
387        boolean after = hasListeners();
388        if (!before && after) {
389            setNotificationEnabled(true);
390        }
391    }
392
393    @Override
394    public synchronized void removeNotificationListener(NotificationListener listener)
395            throws ListenerNotFoundException {
396        boolean before = hasListeners();
397        super.removeNotificationListener(listener);
398        boolean after = hasListeners();
399        if (before && !after) {
400            setNotificationEnabled(false);
401        }
402    }
403
404    @Override
405    public synchronized void removeNotificationListener(NotificationListener listener,
406            NotificationFilter filter,
407            Object handback)
408            throws ListenerNotFoundException {
409        boolean before = hasListeners();
410        super.removeNotificationListener(listener, filter, handback);
411        boolean after = hasListeners();
412        if (before && !after) {
413            setNotificationEnabled(false);
414        }
415    }
416
417    private native void setNotificationEnabled(boolean enabled);
418    private native String[] getDiagnosticCommands();
419    private native DiagnosticCommandInfo[] getDiagnosticCommandInfo(String[] commands);
420    private native String executeDiagnosticCommand(String command);
421
422}
423