1 /*
2  * Copyright (c) 2000, 2008, 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 package javax.management.openmbean;
27
28 import com.sun.jmx.mbeanserver.GetPropertyAction;
29 import java.io.IOException;
30 import java.io.InvalidObjectException;
31 import java.io.ObjectInputStream;
32 import java.io.Serializable;
33 import java.security.AccessController;
34 import java.security.PrivilegedAction;
35 import java.util.Arrays;
36 import java.util.Collections;
37 import java.util.List;
38 import javax.management.Descriptor;
39 import javax.management.ImmutableDescriptor;
40
41 /**
42  * The <code>OpenType</code> class is the parent abstract class of all classes which describe the actual <i>open type</i>
43  * of open data values.
44  * <p>
45  * An <i>open type</i> is defined by:
46  * <ul>
47  *  <li>the fully qualified Java class name of the open data values this type describes;
48  *      note that only a limited set of Java classes is allowed for open data values
49  *      (see {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}),</li>
50  *  <li>its name,</li>
51  *  <li>its description.</li>
52  * </ul>
53  *
54  * @param <T> the Java type that instances described by this type must
55  * have.  For example, {@link SimpleType#INTEGER} is a {@code
56  * SimpleType<Integer>} which is a subclass of {@code OpenType<Integer>},
57  * meaning that an attribute, parameter, or return value that is described
58  * as a {@code SimpleType.INTEGER} must have Java type
59  * {@link Integer}.
60  *
61  * @since 1.5
62  */

63 public abstract class OpenType<T> implements Serializable {
64
65     /* Serial version */
66     static final long serialVersionUID = -9195195325186646468L;
67
68
69     /**
70      * List of the fully qualified names of the Java classes allowed for open
71      * data values. A multidimensional array of any one of these classes or
72      * their corresponding primitive types is also an allowed class for open
73      * data values.
74      *
75        <pre>ALLOWED_CLASSNAMES_LIST = {
76         "java.lang.Void",
77         "java.lang.Boolean",
78         "java.lang.Character",
79         "java.lang.Byte",
80         "java.lang.Short",
81         "java.lang.Integer",
82         "java.lang.Long",
83         "java.lang.Float",
84         "java.lang.Double",
85         "java.lang.String",
86         "java.math.BigDecimal",
87         "java.math.BigInteger",
88         "java.util.Date",
89         "javax.management.ObjectName",
90         CompositeData.class.getName(),
91         TabularData.class.getName() } ;
92        </pre>
93      *
94      */

95     public static final List<String> ALLOWED_CLASSNAMES_LIST =
96       Collections.unmodifiableList(
97         Arrays.asList(
98           "java.lang.Void",
99           "java.lang.Boolean",
100           "java.lang.Character",
101           "java.lang.Byte",
102           "java.lang.Short",
103           "java.lang.Integer",
104           "java.lang.Long",
105           "java.lang.Float",
106           "java.lang.Double",
107           "java.lang.String",
108           "java.math.BigDecimal",
109           "java.math.BigInteger",
110           "java.util.Date",
111           "javax.management.ObjectName",
112           CompositeData.class.getName(),        // better refer to these two class names like this, rather than hardcoding a string,
113           TabularData.class.getName()) );       // in case the package of these classes should change (who knows...)
114
115
116     /**
117      * @deprecated Use {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST} instead.
118      */

119     @Deprecated
120     public static final String[] ALLOWED_CLASSNAMES =
121         ALLOWED_CLASSNAMES_LIST.toArray(new String[0]);
122
123
124     /**
125      * @serial The fully qualified Java class name of open data values this
126      *         type describes.
127      */

128     private String className;
129
130     /**
131      * @serial The type description (should not be null or empty).
132      */

133     private String description;
134
135     /**
136      * @serial The name given to this type (should not be null or empty).
137      */

138     private String typeName;
139
140     /**
141      * Tells if this type describes an array (checked in constructor).
142      */

143     private transient boolean isArray = false;
144
145     /**
146      * Cached Descriptor for this OpenType, constructed on demand.
147      */

148     private transient Descriptor descriptor;
149
150     /* *** Constructor *** */
151
152     /**
153      * Constructs an <code>OpenType</code> instance (actually a subclass instance as <code>OpenType</code> is abstract),
154      * checking for the validity of the given parameters.
155      * The validity constraints are described below for each parameter.
156      * <br>&nbsp;
157      * @param  className  The fully qualified Java class name of the open data values this open type describes.
158      *                    The valid Java class names allowed for open data values are listed in
159      *                    {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
160      *                    A multidimensional array of any one of these classes
161      *                    or their corresponding primitive types is also an allowed class,
162      *                    in which case the class name follows the rules defined by the method
163      *                    {@link Class#getName() getName()} of <code>java.lang.Class</code>.
164      *                    For example, a 3-dimensional array of Strings has for class name
165      *                    &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes).
166      * <br>&nbsp;
167      * @param  typeName  The name given to the open type this instance represents; cannot be a null or empty string.
168      * <br>&nbsp;
169      * @param  description  The human readable description of the open type this instance represents;
170      *                      cannot be a null or empty string.
171      * <br>&nbsp;
172      * @throws IllegalArgumentException  if <var>className</var>, <var>typeName</var> or <var>description</var>
173      *                                   is a null or empty string
174      * <br>&nbsp;
175      * @throws OpenDataException  if <var>className</var> is not one of the allowed Java class names for open data
176      */

177     protected OpenType(String  className,
178                        String  typeName,
179                        String  description) throws OpenDataException {
180         checkClassNameOverride();
181         this.typeName = valid("typeName", typeName);
182         this.description = valid("description", description);
183         this.className = validClassName(className);
184         this.isArray = (this.className != null && this.className.startsWith("["));
185     }
186
187     /* Package-private constructor for callers we trust to get it right. */
188     OpenType(String className, String typeName, String description,
189              boolean isArray) {
190         this.className   = valid("className",className);
191         this.typeName    = valid("typeName", typeName);
192         this.description = valid("description", description);
193         this.isArray     = isArray;
194     }
195
196     private void checkClassNameOverride() throws SecurityException {
197         if (this.getClass().getClassLoader() == null)
198             return;  // We trust bootstrap classes.
199         if (overridesGetClassName(this.getClass())) {
200             final GetPropertyAction getExtendOpenTypes =
201                 new GetPropertyAction("jmx.extend.open.types");
202             if (AccessController.doPrivileged(getExtendOpenTypes) == null) {
203                 throw new SecurityException("Cannot override getClassName() " +
204                         "unless -Djmx.extend.open.types");
205             }
206         }
207     }
208
209     private static boolean overridesGetClassName(final Class<?> c) {
210         return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
211             public Boolean run() {
212                 try {
213                     return (c.getMethod("getClassName").getDeclaringClass() !=
214                             OpenType.class);
215                 } catch (Exception e) {
216                     return true;  // fail safe
217                 }
218             }
219         });
220     }
221
222     private static String validClassName(String className) throws OpenDataException {
223         className   = valid("className", className);
224
225         // Check if className describes an array class, and determines its elements' class name.
226         // (eg: a 3-dimensional array of Strings has for class name: "[[[Ljava.lang.String;")
227         //
228         int n = 0;
229         while (className.startsWith("[", n)) {
230             n++;
231         }
232         String eltClassName; // class name of array elements
233         boolean isPrimitiveArray = false;
234         if (n > 0) {
235             if (className.startsWith("L", n) && className.endsWith(";")) {
236                 // removes the n leading '[' + the 'L' characters
237                 // and the last ';' character
238                 eltClassName = className.substring(n+1, className.length()-1);
239             } else if (n == className.length() - 1) {
240                 // removes the n leading '[' characters
241                 eltClassName = className.substring(n, className.length());
242                 isPrimitiveArray = true;
243             } else {
244                 throw new OpenDataException("Argument className=\"" + className +
245                         "\" is not a valid class name");
246             }
247         } else {
248             // not an array
249             eltClassName = className;
250         }
251
252         // Check that eltClassName's value is one of the allowed basic data types for open data
253         //
254         boolean ok = false;
255         if (isPrimitiveArray) {
256             ok = ArrayType.isPrimitiveContentType(eltClassName);
257         } else {
258             ok = ALLOWED_CLASSNAMES_LIST.contains(eltClassName);
259         }
260         if ( ! ok ) {
261             throw new OpenDataException("Argument className=\""+ className +
262                                         "\" is not one of the allowed Java class names for open data.");
263         }
264
265         return className;
266     }
267
268     /* Return argValue.trim() provided argValue is neither null nor empty;
269        otherwise throw IllegalArgumentException.  */

270     private static String valid(String argName, String argValue) {
271         if (argValue == null || (argValue = argValue.trim()).equals(""))
272             throw new IllegalArgumentException("Argument " + argName +
273                                                " cannot be null or empty");
274         return argValue;
275     }
276
277     /* Package-private access to a Descriptor containing this OpenType. */
278     synchronized Descriptor getDescriptor() {
279         if (descriptor == null) {
280             descriptor = new ImmutableDescriptor(new String[] {"openType"},
281                                                  new Object[] {this});
282         }
283         return descriptor;
284     }
285
286     /* *** Open type information methods *** */
287
288     /**
289      * Returns the fully qualified Java class name of the open data values
290      * this open type describes.
291      * The only possible Java class names for open data values are listed in
292      * {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST}.
293      * A multidimensional array of any one of these classes or their
294      * corresponding primitive types is also an allowed class,
295      * in which case the class name follows the rules defined by the method
296      * {@link Class#getName() getName()} of <code>java.lang.Class</code>.
297      * For example, a 3-dimensional array of Strings has for class name
298      * &quot;<code>[[[Ljava.lang.String;</code>&quot; (without the quotes),
299      * a 3-dimensional array of Integers has for class name
300      * &quot;<code>[[[Ljava.lang.Integer;</code>&quot; (without the quotes),
301      * and a 3-dimensional array of int has for class name
302      * &quot;<code>[[[I</code>&quot; (without the quotes)
303      *
304      * @return the class name.
305      */

306     public String getClassName() {
307         return className;
308     }
309
310     // A version of getClassName() that can only be called from within this
311     // package and that cannot be overridden.
312     String safeGetClassName() {
313         return className;
314     }
315
316     /**
317      * Returns the name of this <code>OpenType</code> instance.
318      *
319      * @return the type name.
320      */

321     public String getTypeName() {
322
323         return typeName;
324     }
325
326     /**
327      * Returns the text description of this <code>OpenType</code> instance.
328      *
329      * @return the description.
330      */

331     public String getDescription() {
332
333         return description;
334     }
335
336     /**
337      * Returns <code>true</code> if the open data values this open
338      * type describes are arrays, <code>false</code> otherwise.
339      *
340      * @return true if this is an array type.
341      */

342     public boolean isArray() {
343
344         return isArray;
345     }
346
347     /**
348      * Tests whether <var>obj</var> is a value for this open type.
349      *
350      * @param obj the object to be tested for validity.
351      *
352      * @return <code>true</code> if <var>obj</var> is a value for this
353      * open type, <code>false</code> otherwise.
354      */

355     public abstract boolean isValue(Object obj) ;
356
357     /**
358      * Tests whether values of the given type can be assigned to this open type.
359      * The default implementation of this method returns true only if the
360      * types are equal.
361      *
362      * @param ot the type to be tested.
363      *
364      * @return true if {@code ot} is assignable to this open type.
365      */

366     boolean isAssignableFrom(OpenType<?> ot) {
367         return this.equals(ot);
368     }
369
370     /* *** Methods overriden from class Object *** */
371
372     /**
373      * Compares the specified <code>obj</code> parameter with this
374      * open type instance for equality.
375      *
376      * @param obj the object to compare to.
377      *
378      * @return true if this object and <code>obj</code> are equal.
379      */

380     public abstract boolean equals(Object obj) ;
381
382     public abstract int hashCode() ;
383
384     /**
385      * Returns a string representation of this open type instance.
386      *
387      * @return the string representation.
388      */

389     public abstract String toString() ;
390
391     /**
392      * Deserializes an {@link OpenType} from an {@link java.io.ObjectInputStream}.
393      */

394     private void readObject(ObjectInputStream in)
395             throws IOException, ClassNotFoundException {
396         checkClassNameOverride();
397         ObjectInputStream.GetField fields = in.readFields();
398         final String classNameField;
399         final String descriptionField;
400         final String typeNameField;
401         try {
402             classNameField =
403                 validClassName((String) fields.get("className"null));
404             descriptionField =
405                 valid("description", (String) fields.get("description"null));
406             typeNameField =
407                 valid("typeName", (String) fields.get("typeName"null));
408         } catch (Exception e) {
409             IOException e2 = new InvalidObjectException(e.getMessage());
410             e2.initCause(e);
411             throw e2;
412         }
413         className = classNameField;
414         description = descriptionField;
415         typeName = typeNameField;
416         isArray = (className.startsWith("["));
417     }
418 }
419