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.lang.reflect.Method;
41import java.security.AccessController;
42import java.lang.System.Logger.Level;
43
44import javax.management.Descriptor;
45import javax.management.DescriptorKey;
46import javax.management.DescriptorAccess;
47import javax.management.MBeanAttributeInfo;
48import javax.management.RuntimeOperationsException;
49
50/**
51 * <p>The ModelMBeanAttributeInfo object describes an attribute of the ModelMBean.
52 * It is a subclass of MBeanAttributeInfo with the addition of an associated Descriptor
53 * and an implementation of the DescriptorAccess interface.</p>
54 *
55 * <P id="descriptor">
56 * The fields in the descriptor are defined, but not limited to, the following.
57 * Note that when the Type in this table is Number, a String that is the decimal
58 * representation of a Long can also be used.</P>
59 *
60 * <table class="striped">
61 * <caption style="display:none">ModelMBeanAttributeInfo Fields</caption>
62 * <tr><th>Name</th><th>Type</th><th>Meaning</th></tr>
63 * <tr><td>name</td><td>String</td>
64 *     <td>Attribute name.</td></tr>
65 * <tr><td>descriptorType</td><td>String</td>
66 *     <td>Must be "attribute".</td></tr>
67 * <tr id="value-field"><td>value</td><td>Object</td>
68 *     <td>Current (cached) value for attribute.</td></tr>
69 * <tr><td>default</td><td>Object</td>
70 *     <td>Default value for attribute.</td></tr>
71 * <tr><td>displayName</td><td>String</td>
72 *     <td>Name of attribute to be used in displays.</td></tr>
73 * <tr><td>getMethod</td><td>String</td>
74 *     <td>Name of operation descriptor for get method.</td></tr>
75 * <tr><td>setMethod</td><td>String</td>
76 *     <td>Name of operation descriptor for set method.</td></tr>
77 * <tr><td>protocolMap</td><td>Descriptor</td>
78 *     <td>See the section "Protocol Map Support" in the JMX specification
79 *         document.  Mappings must be appropriate for the attribute and entries
80 *         can be updated or augmented at runtime.</td></tr>
81 * <tr><td>persistPolicy</td><td>String</td>
82 *     <td>One of: OnUpdate|OnTimer|NoMoreOftenThan|OnUnregister|Always|Never.
83 *         See the section "MBean Descriptor Fields" in the JMX specification
84 *         document.</td></tr>
85 * <tr><td>persistPeriod</td><td>Number</td>
86 *     <td>Frequency of persist cycle in seconds. Used when persistPolicy is
87 *         "OnTimer" or "NoMoreOftenThan".</td></tr>
88 * <tr><td>currencyTimeLimit</td><td>Number</td>
89 *     <td>How long <a href="#value=field">value</a> is valid: &lt;0 never,
90 *         =0 always, &gt;0 seconds.</td></tr>
91 * <tr><td>lastUpdatedTimeStamp</td><td>Number</td>
92 *     <td>When <a href="#value-field">value</a> was set.</td></tr>
93 * <tr><td>visibility</td><td>Number</td>
94 *     <td>1-4 where 1: always visible, 4: rarely visible.</td></tr>
95 * <tr><td>presentationString</td><td>String</td>
96 *     <td>XML formatted string to allow presentation of data.</td></tr>
97 * </table>
98 *
99 * <p>The default descriptor contains the name, descriptorType and displayName
100 * fields.  The default value of the name and displayName fields is the name of
101 * the attribute.</p>
102 *
103 * <p><b>Note:</b> because of inconsistencies in previous versions of
104 * this specification, it is recommended not to use negative or zero
105 * values for <code>currencyTimeLimit</code>.  To indicate that a
106 * cached value is never valid, omit the
107 * <code>currencyTimeLimit</code> field.  To indicate that it is
108 * always valid, use a very large number for this field.</p>
109 *
110 * <p>The <b>serialVersionUID</b> of this class is <code>6181543027787327345L</code>.
111 *
112 * @since 1.5
113 */
114
115@SuppressWarnings("serial")  // serialVersionUID is not constant
116public class ModelMBeanAttributeInfo
117    extends MBeanAttributeInfo
118    implements DescriptorAccess {
119
120    // Serialization compatibility stuff:
121    // Two serial forms are supported in this class. The selected form depends
122    // on system property "jmx.serial.form":
123    //  - "1.0" for JMX 1.0
124    //  - any other value for JMX 1.1 and higher
125    //
126    // Serial version for old serial form
127    private static final long oldSerialVersionUID = 7098036920755973145L;
128    //
129    // Serial version for new serial form
130    private static final long newSerialVersionUID = 6181543027787327345L;
131    //
132    // Serializable fields in old serial form
133    private static final ObjectStreamField[] oldSerialPersistentFields =
134    {
135      new ObjectStreamField("attrDescriptor", Descriptor.class),
136      new ObjectStreamField("currClass", String.class)
137    };
138    //
139    // Serializable fields in new serial form
140    private static final ObjectStreamField[] newSerialPersistentFields =
141    {
142      new ObjectStreamField("attrDescriptor", Descriptor.class)
143    };
144    //
145    // Actual serial version and serial form
146    private static final long serialVersionUID;
147    /**
148     * @serialField attrDescriptor Descriptor The {@link Descriptor}
149     * containing the metadata corresponding to this attribute
150     */
151    private static final ObjectStreamField[] serialPersistentFields;
152    private static boolean compat = false;
153    static {
154        try {
155            GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
156            String form = AccessController.doPrivileged(act);
157            compat = (form != null && form.equals("1.0"));
158        } catch (Exception e) {
159            // OK: No compat with 1.0
160        }
161        if (compat) {
162            serialPersistentFields = oldSerialPersistentFields;
163            serialVersionUID = oldSerialVersionUID;
164        } else {
165            serialPersistentFields = newSerialPersistentFields;
166            serialVersionUID = newSerialVersionUID;
167        }
168    }
169    //
170    // END Serialization compatibility stuff
171
172        /**
173         * @serial The {@link Descriptor} containing the metadata corresponding to
174         * this attribute
175         */
176        private Descriptor attrDescriptor = validDescriptor(null);
177
178        private final static String currClass = "ModelMBeanAttributeInfo";
179
180        /**
181         * Constructs a ModelMBeanAttributeInfo object with a default
182         * descriptor. The {@link Descriptor} of the constructed
183         * object will include fields contributed by any annotations
184         * on the {@code Method} objects that contain the {@link
185         * DescriptorKey} meta-annotation.
186         *
187         * @param name The name of the attribute.
188         * @param description A human readable description of the attribute. Optional.
189         * @param getter The method used for reading the attribute value.
190         *          May be null if the property is write-only.
191         * @param setter The method used for writing the attribute value.
192         *          May be null if the attribute is read-only.
193         * @exception javax.management.IntrospectionException There is a consistency
194         * problem in the definition of this attribute.
195         *
196         */
197
198        public ModelMBeanAttributeInfo(String name,
199                                       String description,
200                                       Method getter,
201                                       Method setter)
202        throws javax.management.IntrospectionException {
203                super(name, description, getter, setter);
204
205                if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
206                    MODELMBEAN_LOGGER.log(Level.TRACE,
207                            "ModelMBeanAttributeInfo(" +
208                            "String,String,Method,Method) " +
209                            "Entry " + name);
210                }
211
212                attrDescriptor = validDescriptor(null);
213                // put getter and setter methods in operations list
214                // create default descriptor
215
216        }
217
218        /**
219         * Constructs a ModelMBeanAttributeInfo object.  The {@link
220         * Descriptor} of the constructed object will include fields
221         * contributed by any annotations on the {@code Method}
222         * objects that contain the {@link DescriptorKey}
223         * meta-annotation.
224         *
225         * @param name The name of the attribute.
226         * @param description A human readable description of the attribute. Optional.
227         * @param getter The method used for reading the attribute value.
228         *          May be null if the property is write-only.
229         * @param setter The method used for writing the attribute value.
230         *          May be null if the attribute is read-only.
231         * @param descriptor An instance of Descriptor containing the
232         * appropriate metadata for this instance of the Attribute. If
233         * it is null, then a default descriptor will be created.  If
234         * the descriptor does not contain the field "displayName" this field is added
235         * in the descriptor with its default value.
236         * @exception javax.management.IntrospectionException There is a consistency
237         * problem in the definition of this attribute.
238         * @exception RuntimeOperationsException Wraps an
239         * IllegalArgumentException. The descriptor is invalid, or descriptor
240         * field "name" is not equal to name parameter, or descriptor field
241         * "descriptorType" is not equal to "attribute".
242         *
243         */
244
245        public ModelMBeanAttributeInfo(String name,
246                                       String description,
247                                       Method getter,
248                                       Method setter,
249                                       Descriptor descriptor)
250        throws javax.management.IntrospectionException {
251
252                super(name, description, getter, setter);
253                // put getter and setter methods in operations list
254                if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
255                    MODELMBEAN_LOGGER.log(Level.TRACE,
256                            "ModelMBeanAttributeInfo(" +
257                            "String,String,Method,Method,Descriptor) " +
258                            "Entry " + name);
259                }
260                attrDescriptor = validDescriptor(descriptor);
261        }
262
263        /**
264         * Constructs a ModelMBeanAttributeInfo object with a default descriptor.
265         *
266         * @param name The name of the attribute
267         * @param type The type or class name of the attribute
268         * @param description A human readable description of the attribute.
269         * @param isReadable True if the attribute has a getter method, false otherwise.
270         * @param isWritable True if the attribute has a setter method, false otherwise.
271         * @param isIs True if the attribute has an "is" getter, false otherwise.
272         *
273         */
274        public ModelMBeanAttributeInfo(String name,
275                                       String type,
276                                       String description,
277                                       boolean isReadable,
278                                       boolean isWritable,
279                                       boolean isIs)
280    {
281
282                super(name, type, description, isReadable, isWritable, isIs);
283                // create default descriptor
284                if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
285                    MODELMBEAN_LOGGER.log(Level.TRACE,
286                            ModelMBeanAttributeInfo.class.getName(),
287                            "ModelMBeanAttributeInfo(" +
288                            "String,String,String,boolean,boolean,boolean)",
289                            "Entry", name);
290                }
291                attrDescriptor = validDescriptor(null);
292        }
293
294        /**
295         * Constructs a ModelMBeanAttributeInfo object.
296         *
297         * @param name The name of the attribute
298         * @param type The type or class name of the attribute
299         * @param description A human readable description of the attribute.
300         * @param isReadable True if the attribute has a getter method, false otherwise.
301         * @param isWritable True if the attribute has a setter method, false otherwise.
302         * @param isIs True if the attribute has an "is" getter, false otherwise.
303         * @param descriptor An instance of Descriptor containing the
304         * appropriate metadata for this instance of the Attribute. If
305         * it is null then a default descriptor will be created.  If
306         * the descriptor does not contain the field "displayName" this field
307         * is added in the descriptor with its default value.
308         * @exception RuntimeOperationsException Wraps an
309         * IllegalArgumentException. The descriptor is invalid, or descriptor
310         * field "name" is not equal to name parameter, or descriptor field
311         * "descriptorType" is not equal to "attribute".
312         *
313         */
314        public ModelMBeanAttributeInfo(String name,
315                                       String type,
316                                       String description,
317                                       boolean isReadable,
318                                       boolean isWritable,
319                                       boolean isIs,
320                                       Descriptor descriptor)
321        {
322                super(name, type, description, isReadable, isWritable, isIs);
323                if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
324                    MODELMBEAN_LOGGER.log(Level.TRACE,
325                            "ModelMBeanAttributeInfo(String,String,String," +
326                            "boolean,boolean,boolean,Descriptor)" +
327                            "Entry " + name);
328                }
329                attrDescriptor = validDescriptor(descriptor);
330        }
331
332        /**
333         * Constructs a new ModelMBeanAttributeInfo object from this
334         * ModelMBeanAttributeInfo Object.  A default descriptor will
335         * be created.
336         *
337         * @param inInfo the ModelMBeanAttributeInfo to be duplicated
338         */
339
340        public ModelMBeanAttributeInfo(ModelMBeanAttributeInfo inInfo)
341        {
342                super(inInfo.getName(),
343                          inInfo.getType(),
344                          inInfo.getDescription(),
345                          inInfo.isReadable(),
346                          inInfo.isWritable(),
347                          inInfo.isIs());
348                if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
349                    MODELMBEAN_LOGGER.log(Level.TRACE,
350                            "ModelMBeanAttributeInfo(ModelMBeanAttributeInfo) " +
351                            "Entry");
352                }
353                Descriptor newDesc = inInfo.getDescriptor();
354                attrDescriptor = validDescriptor(newDesc);
355        }
356
357        /**
358         * Gets a copy of the associated Descriptor for the
359         * ModelMBeanAttributeInfo.
360         *
361         * @return Descriptor associated with the
362         * ModelMBeanAttributeInfo object.
363         *
364         * @see #setDescriptor
365         */
366
367        public Descriptor getDescriptor() {
368            if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
369                MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
370            }
371                if (attrDescriptor == null) {
372                    attrDescriptor = validDescriptor(null);
373                }
374                return((Descriptor)attrDescriptor.clone());
375        }
376
377
378        /**
379        * Sets associated Descriptor (full replace) for the
380        * ModelMBeanAttributeDescriptor.  If the new Descriptor is
381        * null, then the associated Descriptor reverts to a default
382        * descriptor.  The Descriptor is validated before it is
383        * assigned.  If the new Descriptor is invalid, then a
384        * RuntimeOperationsException wrapping an
385        * IllegalArgumentException is thrown.
386        * @param inDescriptor replaces the Descriptor associated with the
387        * ModelMBeanAttributeInfo
388        *
389        * @exception RuntimeOperationsException Wraps an
390        * IllegalArgumentException for an invalid Descriptor
391        *
392        * @see #getDescriptor
393        */
394        public void setDescriptor(Descriptor inDescriptor) {
395            attrDescriptor =  validDescriptor(inDescriptor);
396        }
397
398        /**
399        * Creates and returns a new ModelMBeanAttributeInfo which is a duplicate of this ModelMBeanAttributeInfo.
400        *
401        * @exception RuntimeOperationsException for illegal value for
402        * field Names or field Values.  If the descriptor construction
403        * fails for any reason, this exception will be thrown.
404        */
405
406        @Override
407        public Object clone()
408        {
409            if (MODELMBEAN_LOGGER.isLoggable(Level.TRACE)) {
410                MODELMBEAN_LOGGER.log(Level.TRACE, "Entry");
411            }
412                return(new ModelMBeanAttributeInfo(this));
413        }
414
415        /**
416        * Returns a human-readable version of the
417        * ModelMBeanAttributeInfo instance.
418        */
419        @Override
420        public String toString()
421        {
422            return
423                "ModelMBeanAttributeInfo: " + this.getName() +
424                " ; Description: " + this.getDescription() +
425                " ; Types: " + this.getType() +
426                " ; isReadable: " + this.isReadable() +
427                " ; isWritable: " + this.isWritable() +
428                " ; Descriptor: " + this.getDescriptor();
429        }
430
431
432        /**
433         * Clones the passed in Descriptor, sets default values, and checks for validity.
434         * If the Descriptor is invalid (for instance by having the wrong "name"),
435         * this indicates programming error and a RuntimeOperationsException will be thrown.
436         *
437         * The following fields will be defaulted if they are not already set:
438         * displayName=this.getName(),name=this.getName(),descriptorType = "attribute"
439         *
440         * @param in Descriptor to be checked, or null which is equivalent to
441         * an empty Descriptor.
442         * @exception RuntimeOperationsException if Descriptor is invalid
443         */
444        private Descriptor validDescriptor(final Descriptor in) throws RuntimeOperationsException {
445
446            Descriptor clone;
447            boolean defaulted = (in == null);
448            if (defaulted) {
449                clone = new DescriptorSupport();
450                MODELMBEAN_LOGGER.log(Level.TRACE, "Null Descriptor, creating new.");
451            } else {
452                clone = (Descriptor) in.clone();
453            }
454
455            //Setting defaults.
456            if (defaulted && clone.getFieldValue("name")==null) {
457                clone.setField("name", this.getName());
458                MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting Descriptor name to " + this.getName());
459            }
460            if (defaulted && clone.getFieldValue("descriptorType")==null) {
461                clone.setField("descriptorType", "attribute");
462                MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting descriptorType to \"attribute\"");
463            }
464            if (clone.getFieldValue("displayName") == null) {
465                clone.setField("displayName",this.getName());
466                MODELMBEAN_LOGGER.log(Level.TRACE, "Defaulting Descriptor displayName to " + this.getName());
467            }
468
469            //Checking validity
470            if (!clone.isValid()) {
471                 throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
472                    "The isValid() method of the Descriptor object itself returned false,"+
473                    "one or more required fields are invalid. Descriptor:" + clone.toString());
474            }
475            if (!getName().equalsIgnoreCase((String)clone.getFieldValue("name"))) {
476                    throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
477                    "The Descriptor \"name\" field does not match the object described. " +
478                     " Expected: "+ this.getName() + " , was: " + clone.getFieldValue("name"));
479            }
480
481            if (!"attribute".equalsIgnoreCase((String)clone.getFieldValue("descriptorType"))) {
482                     throw new RuntimeOperationsException(new IllegalArgumentException("Invalid Descriptor argument"),
483                    "The Descriptor \"descriptorType\" field does not match the object described. " +
484                     " Expected: \"attribute\" ," + " was: " + clone.getFieldValue("descriptorType"));
485            }
486
487            return clone;
488        }
489
490
491    /**
492     * Deserializes a {@link ModelMBeanAttributeInfo} from an {@link ObjectInputStream}.
493     */
494    private void readObject(ObjectInputStream in)
495            throws IOException, ClassNotFoundException {
496      // New serial form ignores extra field "currClass"
497      in.defaultReadObject();
498    }
499
500
501    /**
502     * Serializes a {@link ModelMBeanAttributeInfo} to an {@link ObjectOutputStream}.
503     */
504    private void writeObject(ObjectOutputStream out)
505            throws IOException {
506      if (compat)
507      {
508        // Serializes this instance in the old serial form
509        //
510        ObjectOutputStream.PutField fields = out.putFields();
511        fields.put("attrDescriptor", attrDescriptor);
512        fields.put("currClass", currClass);
513        out.writeFields();
514      }
515      else
516      {
517        // Serializes this instance in the new serial form
518        //
519        out.defaultWriteObject();
520      }
521    }
522
523}
524