1/*
2 * Copyright (c) 2000, 2017, 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/*
26 * @author    IBM Corp.
27 *
28 * Copyright IBM Corp. 1999-2000.  All rights reserved.
29 */
30
31package javax.management.modelmbean;
32
33import static com.sun.jmx.defaults.JmxProperties.MODELMBEAN_LOGGER;
34import com.sun.jmx.mbeanserver.GetPropertyAction;
35
36import java.io.IOException;
37import java.io.ObjectInputStream;
38import java.io.ObjectOutputStream;
39import java.io.ObjectStreamField;
40import java.security.AccessController;
41import java.lang.System.Logger.Level;
42
43import javax.management.Descriptor;
44import javax.management.DescriptorAccess;
45import javax.management.MBeanNotificationInfo;
46import javax.management.RuntimeOperationsException;
47
48/**
49 * <p>The ModelMBeanNotificationInfo object describes a notification emitted
50 * by a ModelMBean.
51 * It is a subclass of MBeanNotificationInfo with the addition of an
52 * associated Descriptor and an implementation of the Descriptor interface.</p>
53 *
54 * <P id="descriptor">
55 * The fields in the descriptor are defined, but not limited to, the following.
56 * Note that when the Type in this table is Number, a String that is the decimal
57 * representation of a Long can also be used.</P>
58 *
59 * <table class="striped">
60 * <caption style="display:none">ModelMBeanNotificationInfo Fields</caption>
61 * <thead>
62 * <tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Meaning</th></tr>
63 * </thead>
64 * <tbody style="text-align:left">
65 * <tr><th scope="row">name</th><td>String</td>
66 *     <td>Notification name.</td></tr>
67 * <tr><th scope="row">descriptorType</th><td>String</td>
68 *     <td>Must be "notification".</td></tr>
69 * <tr><th scope="row">severity</th><td>Number</td>
70 *     <td>0-6 where 0: unknown; 1: non-recoverable;
71 *         2: critical, failure; 3: major, severe;
72 *         4: minor, marginal, error; 5: warning;
73 *         6: normal, cleared, informative</td></tr>
74 * <tr><th scope="row">messageID</th><td>String</td>
75 *     <td>Unique key for message text (to allow translation, analysis).</td></tr>
76 * <tr><th scope="row">messageText</th><td>String</td>
77 *     <td>Text of notification.</td></tr>
78 * <tr><th scope="row">log</th><td>String</td>
79 *     <td>T - log message, F - do not log message.</td></tr>
80 * <tr><th scope="row">logfile</th><td>String</td>
81 *     <td>fully qualified file name appropriate for operating system.</td></tr>
82 * <tr><th scope="row">visibility</th><td>Number</td>
83 *     <td>1-4 where 1: always visible 4: rarely visible.</td></tr>
84 * <tr><th scope="row">presentationString</th><td>String</td>
85 *     <td>XML formatted string to allow presentation of data.</td></tr>
86 * </tbody>
87 * </table>
88 *
89 * <p>The default descriptor contains the name, descriptorType,
90 * displayName and severity(=6) fields.  The default value of the name
91 * and displayName fields is the name of the Notification class (as
92 * specified by the <code>name</code> parameter of the
93 * ModelMBeanNotificationInfo constructor).</p>
94 *
95 * <p>The <b>serialVersionUID</b> of this class is <code>-7445681389570207141L</code>.
96 *
97 * @since 1.5
98 */
99
100@SuppressWarnings("serial")  // serialVersionUID is not constant
101public class ModelMBeanNotificationInfo
102    extends MBeanNotificationInfo
103    implements DescriptorAccess {
104
105    // Serialization compatibility stuff:
106    // Two serial forms are supported in this class. The selected form
107    // depends on system property "jmx.serial.form":
108    //  - "1.0" for JMX 1.0
109    //  - any other value for JMX 1.1 and higher
110    //
111    // Serial version for old serial form
112    private static final long oldSerialVersionUID = -5211564525059047097L;
113    //
114    // Serial version for new serial form
115    private static final long newSerialVersionUID = -7445681389570207141L;
116    //
117    // Serializable fields in old serial form
118    private static final ObjectStreamField[] oldSerialPersistentFields =
119    {
120      new ObjectStreamField("notificationDescriptor", Descriptor.class),
121      new ObjectStreamField("currClass", String.class)
122    };
123    //
124    // Serializable fields in new serial form
125    private static final ObjectStreamField[] newSerialPersistentFields =
126    {
127      new ObjectStreamField("notificationDescriptor", Descriptor.class)
128    };
129    //
130    // Actual serial version and serial form
131    private static final long serialVersionUID;
132    /**
133     * @serialField notificationDescriptor Descriptor The descriptor
134     *   containing the appropriate metadata for this instance
135     */
136    private static final ObjectStreamField[] serialPersistentFields;
137    private static boolean compat = false;
138    static {
139        try {
140            GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
141            String form = AccessController.doPrivileged(act);
142            compat = (form != null && form.equals("1.0"));
143        } catch (Exception e) {
144            // OK: No compat with 1.0
145        }
146        if (compat) {
147            serialPersistentFields = oldSerialPersistentFields;
148            serialVersionUID = oldSerialVersionUID;
149        } else {
150            serialPersistentFields = newSerialPersistentFields;
151            serialVersionUID = newSerialVersionUID;
152        }
153    }
154    //
155    // END Serialization compatibility stuff
156
157    /**
158     * @serial The descriptor containing the appropriate metadata for
159     *         this instance
160     */
161    private Descriptor notificationDescriptor;
162
163    private static final String currClass = "ModelMBeanNotificationInfo";
164
165    /**
166     * Constructs a ModelMBeanNotificationInfo object with a default
167     * descriptor.
168     *
169     * @param notifTypes The array of strings (in dot notation) containing
170     *     the notification types that may be emitted.
171     * @param name The name of the Notification class.
172     * @param description A human readable description of the
173     *     Notification. Optional.
174     **/
175    public ModelMBeanNotificationInfo(String[] notifTypes,
176                                      String name,
177                                      String description) {
178        this(notifTypes,name,description,null);
179    }
180
181    /**
182     * Constructs a ModelMBeanNotificationInfo object.
183     *
184     * @param notifTypes The array of strings (in dot notation)
185     *        containing the notification types that may be emitted.
186     * @param name The name of the Notification class.
187     * @param description A human readable description of the Notification.
188     *        Optional.
189     * @param descriptor An instance of Descriptor containing the
190     *        appropriate metadata for this instance of the
191     *        MBeanNotificationInfo. If it is null a default descriptor
192     *        will be created. If the descriptor does not contain the
193     *        fields "displayName" or "severity",
194     *        the missing ones are added with their default values.
195     *
196     * @exception RuntimeOperationsException Wraps an
197     *    {@link IllegalArgumentException}. The descriptor is invalid, or
198     *    descriptor field "name" is not equal to parameter name, or
199     *    descriptor field "descriptorType" is not equal to "notification".
200     *
201     **/
202    public ModelMBeanNotificationInfo(String[] notifTypes,
203                                      String name,
204                                      String description,
205                                      Descriptor descriptor) {
206        super(notifTypes, name, description);
207        if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
208            MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
209        }
210        notificationDescriptor = validDescriptor(descriptor);
211    }
212
213    /**
214     * Constructs a new ModelMBeanNotificationInfo object from this
215     * ModelMBeanNotfication Object.
216     *
217     * @param inInfo the ModelMBeanNotificationInfo to be duplicated
218     *
219     **/
220    public ModelMBeanNotificationInfo(ModelMBeanNotificationInfo inInfo) {
221        this(inInfo.getNotifTypes(),
222             inInfo.getName(),
223             inInfo.getDescription(),inInfo.getDescriptor());
224    }
225
226    /**
227     * Creates and returns a new ModelMBeanNotificationInfo which is a
228     * duplicate of this ModelMBeanNotificationInfo.
229     **/
230    public Object clone () {
231        if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
232            MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
233        }
234        return(new ModelMBeanNotificationInfo(this));
235    }
236
237    /**
238     * Returns a copy of the associated Descriptor for the
239     * ModelMBeanNotificationInfo.
240     *
241     * @return Descriptor associated with the
242     * ModelMBeanNotificationInfo object.
243     *
244     * @see #setDescriptor
245     **/
246    public Descriptor getDescriptor() {
247        if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
248            MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
249        }
250
251        if (notificationDescriptor == null) {
252            // Dead code. Should never happen.
253            if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
254                MODELMBEAN_LOGGER.log(Level.TRACE,
255                        "Descriptor value is null, " +
256                        "setting descriptor to default values");
257            }
258            notificationDescriptor = validDescriptor(null);
259        }
260
261        return((Descriptor)notificationDescriptor.clone());
262    }
263
264    /**
265     * Sets associated Descriptor (full replace) for the
266     * ModelMBeanNotificationInfo If the new Descriptor is null,
267     * then the associated Descriptor reverts to a default
268     * descriptor.  The Descriptor is validated before it is
269     * assigned.  If the new Descriptor is invalid, then a
270     * RuntimeOperationsException wrapping an
271     * IllegalArgumentException is thrown.
272     *
273     * @param inDescriptor replaces the Descriptor associated with the
274     * ModelMBeanNotification interface
275     *
276     * @exception RuntimeOperationsException Wraps an
277     * {@link IllegalArgumentException} for invalid Descriptor.
278     *
279     * @see #getDescriptor
280     **/
281    public void setDescriptor(Descriptor inDescriptor) {
282        if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
283            MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
284        }
285        notificationDescriptor = validDescriptor(inDescriptor);
286    }
287
288    /**
289     * Returns a human readable string containing
290     * ModelMBeanNotificationInfo.
291     *
292     * @return a string describing this object.
293     **/
294    public String toString() {
295        if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
296            MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
297        }
298
299        final StringBuilder retStr = new StringBuilder();
300
301        retStr.append("ModelMBeanNotificationInfo: ")
302            .append(this.getName());
303
304        retStr.append(" ; Description: ")
305            .append(this.getDescription());
306
307        retStr.append(" ; Descriptor: ")
308            .append(this.getDescriptor());
309
310        retStr.append(" ; Types: ");
311        String[] nTypes = this.getNotifTypes();
312        for (int i=0; i < nTypes.length; i++) {
313            if (i > 0) retStr.append(", ");
314            retStr.append(nTypes[i]);
315        }
316        return retStr.toString();
317    }
318
319
320    /**
321     * Clones the passed in Descriptor, sets default values, and checks for validity.
322     * If the Descriptor is invalid (for instance by having the wrong "name"),
323     * this indicates programming error and a RuntimeOperationsException will be thrown.
324     *
325     * The following fields will be defaulted if they are not already set:
326     * descriptorType="notification",displayName=this.getName(),
327     * name=this.getName(),severity="6"
328     *
329     *
330     * @param in Descriptor to be checked, or null which is equivalent to an
331     * empty Descriptor.
332     * @exception RuntimeOperationsException if Descriptor is invalid
333     */
334    private Descriptor validDescriptor(final Descriptor in) throws RuntimeOperationsException {
335        Descriptor clone;
336        boolean defaulted = (in == null);
337        if (defaulted) {
338            clone = new DescriptorSupport();
339            MODELMBEAN_LOGGER.log(Level.TRACE, "Null Descriptor, creating new.");
340        } else {
341            clone = (Descriptor) in.clone();
342        }
343
344        //Setting defaults.
345        if (defaulted && clone.getFieldValue("name")==null) {
346            clone.setField("name", this.getName());
347            MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting Descriptor name to " + this.getName());
348        }
349        if (defaulted && clone.getFieldValue("descriptorType")==null) {
350            clone.setField("descriptorType", "notification");
351            MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting descriptorType to \"notification\"");
352        }
353        if (clone.getFieldValue("displayName") == null) {
354            clone.setField("displayName",this.getName());
355            MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting Descriptor displayName to " + this.getName());
356        }
357        if (clone.getFieldValue("severity") == null) {
358            clone.setField("severity", "6");
359            MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting Descriptor severity field to 6");
360        }
361
362        //Checking validity
363        if (!clone.isValid()) {
364             throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
365                "The isValid() method of the Descriptor object itself returned false,"+
366                "one or more required fields are invalid. Descriptor:" + clone.toString());
367        }
368        if (!getName().equalsIgnoreCase((String) clone.getFieldValue("name"))) {
369                throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
370                "The Descriptor \"name\" field does not match the object described. " +
371                 " Expected: "+ this.getName() + " , was: " + clone.getFieldValue("name"));
372        }
373        if (!"notification".equalsIgnoreCase((String) clone.getFieldValue("descriptorType"))) {
374                 throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
375                "The Descriptor \"descriptorType\" field does not match the object described. " +
376                 " Expected: \"notification\" ," + " was: " + clone.getFieldValue("descriptorType"));
377        }
378
379        return clone;
380    }
381
382
383    /**
384     * Deserializes a {@link ModelMBeanNotificationInfo} from an
385     * {@link ObjectInputStream}.
386     **/
387    private void readObject(ObjectInputStream in)
388        throws IOException, ClassNotFoundException {
389        // New serial form ignores extra field "currClass"
390        in.defaultReadObject();
391    }
392
393
394    /**
395     * Serializes a {@link ModelMBeanNotificationInfo} to an
396     * {@link ObjectOutputStream}.
397     **/
398    private void writeObject(ObjectOutputStream out)
399        throws IOException {
400        if (compat) {
401            // Serializes this instance in the old serial form
402            //
403            ObjectOutputStream.PutField fields = out.putFields();
404            fields.put("notificationDescriptor", notificationDescriptor);
405            fields.put("currClass", currClass);
406            out.writeFields();
407        } else {
408            // Serializes this instance in the new serial form
409            //
410            out.defaultWriteObject();
411        }
412    }
413
414}
415