1 /*
2  * Copyright (c) 2000, 2021, 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.relation;
27 
28 import static com.sun.jmx.defaults.JmxProperties.RELATION_LOGGER;
29 import static com.sun.jmx.mbeanserver.Util.cast;
30 import com.sun.jmx.mbeanserver.GetPropertyAction;
31 
32 import java.io.IOException;
33 import java.io.ObjectInputStream;
34 import java.io.ObjectOutputStream;
35 import java.io.ObjectStreamField;
36 
37 import java.security.AccessController;
38 
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.lang.System.Logger.Level;
46 
47 /**
48  * A RelationTypeSupport object implements the RelationType interface.
49  * <P>It represents a relation type, providing role information for each role
50  * expected to be supported in every relation of that type.
51  *
52  * <P>A relation type includes a relation type name and a list of
53  * role infos (represented by RoleInfo objects).
54  *
55  * <P>A relation type has to be declared in the Relation Service:
56  * <P>- either using the createRelationType() method, where a RelationTypeSupport
57  * object will be created and kept in the Relation Service
58  * <P>- either using the addRelationType() method where the user has to create
59  * an object implementing the RelationType interface, and this object will be
60  * used as representing a relation type in the Relation Service.
61  *
62  * <p>The <b>serialVersionUID</b> of this class is <code>4611072955724144607L</code>.
63  *
64  * @since 1.5
65  */
66 @SuppressWarnings("serial")  // serialVersionUID not constant
67 public class RelationTypeSupport implements RelationType {
68 
69     // Serialization compatibility stuff:
70     // Two serial forms are supported in this class. The selected form depends
71     // on system property "jmx.serial.form":
72     //  - "1.0" for JMX 1.0
73     //  - any other value for JMX 1.1 and higher
74     //
75     // Serial version for old serial form
76     private static final long oldSerialVersionUID = -8179019472410837190L;
77     //
78     // Serial version for new serial form
79     private static final long newSerialVersionUID = 4611072955724144607L;
80     //
81     // Serializable fields in old serial form
82     private static final ObjectStreamField[] oldSerialPersistentFields =
83     {
84       new ObjectStreamField("myTypeName", String.class),
85       new ObjectStreamField("myRoleName2InfoMap", HashMap.class),
86       new ObjectStreamField("myIsInRelServFlg", boolean.class)
87     };
88     //
89     // Serializable fields in new serial form
90     private static final ObjectStreamField[] newSerialPersistentFields =
91     {
92       new ObjectStreamField("typeName", String.class),
93       new ObjectStreamField("roleName2InfoMap", Map.class),
94       new ObjectStreamField("isInRelationService", boolean.class)
95     };
96     //
97     // Actual serial version and serial form
98     private static final long serialVersionUID;
99     /**
100      * @serialField typeName String Relation type name
101      * @serialField roleName2InfoMap Map {@link Map} holding the mapping:
102      *              &lt;role name ({@link String})&gt; -&gt; &lt;role info ({@link RoleInfo} object)&gt;
103      * @serialField isInRelationService boolean Flag specifying whether the relation type has been declared in the
104      *              Relation Service (so can no longer be updated)
105      */
106     private static final ObjectStreamField[] serialPersistentFields;
107     private static boolean compat = false;
108     static {
109         try {
110             GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
111             @SuppressWarnings("removal")
112             String form = AccessController.doPrivileged(act);
113             compat = (form != null && form.equals("1.0"));
114         } catch (Exception e) {
115             // OK : Too bad, no compat with 1.0
116         }
117         if (compat) {
118             serialPersistentFields = oldSerialPersistentFields;
119             serialVersionUID = oldSerialVersionUID;
120         } else {
121             serialPersistentFields = newSerialPersistentFields;
122             serialVersionUID = newSerialVersionUID;
123         }
124     }
125     //
126     // END Serialization compatibility stuff
127 
128     //
129     // Private members
130     //
131 
132     /**
133      * @serial Relation type name
134      */
135     private String typeName = null;
136 
137     /**
138      * @serial {@link Map} holding the mapping:
139      *           &lt;role name ({@link String})&gt; -&gt; &lt;role info ({@link RoleInfo} object)&gt;
140      */
141     private Map<String,RoleInfo> roleName2InfoMap =
142         new HashMap<String,RoleInfo>();
143 
144     /**
145      * @serial Flag specifying whether the relation type has been declared in the
146      *         Relation Service (so can no longer be updated)
147      */
148     private boolean isInRelationService = false;
149 
150     //
151     // Constructors
152     //
153 
154     /**
155      * Constructor where all role definitions are dynamically created and
156      * passed as parameter.
157      *
158      * @param relationTypeName  Name of relation type
159      * @param roleInfoArray  List of role definitions (RoleInfo objects)
160      *
161      * @exception IllegalArgumentException  if null parameter
162      * @exception InvalidRelationTypeException  if:
163      * <P>- the same name has been used for two different roles
164      * <P>- no role info provided
165      * <P>- one null role info provided
166      */
RelationTypeSupport(String relationTypeName, RoleInfo[] roleInfoArray)167     public RelationTypeSupport(String relationTypeName,
168                             RoleInfo[] roleInfoArray)
169         throws IllegalArgumentException,
170                InvalidRelationTypeException {
171 
172         if (relationTypeName == null || roleInfoArray == null) {
173             String excMsg = "Invalid parameter.";
174             throw new IllegalArgumentException(excMsg);
175         }
176 
177         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
178 
179         // Can throw InvalidRelationTypeException, ClassNotFoundException
180         // and NotCompliantMBeanException
181         initMembers(relationTypeName, roleInfoArray);
182 
183         RELATION_LOGGER.log(Level.TRACE, "RETURN");
184         return;
185     }
186 
187     /**
188      * Constructor to be used for subclasses.
189      *
190      * @param relationTypeName  Name of relation type.
191      *
192      * @exception IllegalArgumentException  if null parameter.
193      */
RelationTypeSupport(String relationTypeName)194     protected RelationTypeSupport(String relationTypeName)
195     {
196         if (relationTypeName == null) {
197             String excMsg = "Invalid parameter.";
198             throw new IllegalArgumentException(excMsg);
199         }
200 
201         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
202 
203         typeName = relationTypeName;
204 
205         RELATION_LOGGER.log(Level.TRACE, "RETURN");
206         return;
207     }
208 
209     //
210     // Accessors
211     //
212 
213     /**
214      * Returns the relation type name.
215      *
216      * @return the relation type name.
217      */
getRelationTypeName()218     public String getRelationTypeName() {
219         return typeName;
220     }
221 
222     /**
223      * Returns the list of role definitions (ArrayList of RoleInfo objects).
224      */
getRoleInfos()225     public List<RoleInfo> getRoleInfos() {
226         return new ArrayList<RoleInfo>(roleName2InfoMap.values());
227     }
228 
229     /**
230      * Returns the role info (RoleInfo object) for the given role info name
231      * (null if not found).
232      *
233      * @param roleInfoName  role info name
234      *
235      * @return RoleInfo object providing role definition
236      * does not exist
237      *
238      * @exception IllegalArgumentException  if null parameter
239      * @exception RoleInfoNotFoundException  if no role info with that name in
240      * relation type.
241      */
getRoleInfo(String roleInfoName)242     public RoleInfo getRoleInfo(String roleInfoName)
243         throws IllegalArgumentException,
244                RoleInfoNotFoundException {
245 
246         if (roleInfoName == null) {
247             String excMsg = "Invalid parameter.";
248             throw new IllegalArgumentException(excMsg);
249         }
250 
251         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", roleInfoName);
252 
253         // No null RoleInfo allowed, so use get()
254         RoleInfo result = roleName2InfoMap.get(roleInfoName);
255 
256         if (result == null) {
257             StringBuilder excMsgStrB = new StringBuilder();
258             String excMsg = "No role info for role ";
259             excMsgStrB.append(excMsg);
260             excMsgStrB.append(roleInfoName);
261             throw new RoleInfoNotFoundException(excMsgStrB.toString());
262         }
263 
264         RELATION_LOGGER.log(Level.TRACE, "RETURN");
265         return result;
266     }
267 
268     //
269     // Misc
270     //
271 
272     /**
273      * Add a role info.
274      * This method of course should not be used after the creation of the
275      * relation type, because updating it would invalidate that the relations
276      * created associated to that type still conform to it.
277      * Can throw a RuntimeException if trying to update a relation type
278      * declared in the Relation Service.
279      *
280      * @param roleInfo  role info to be added.
281      *
282      * @exception IllegalArgumentException  if null parameter.
283      * @exception InvalidRelationTypeException  if there is already a role
284      *  info in current relation type with the same name.
285      */
addRoleInfo(RoleInfo roleInfo)286     protected void addRoleInfo(RoleInfo roleInfo)
287         throws IllegalArgumentException,
288                InvalidRelationTypeException {
289 
290         if (roleInfo == null) {
291             String excMsg = "Invalid parameter.";
292             throw new IllegalArgumentException(excMsg);
293         }
294 
295         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", roleInfo);
296 
297         if (isInRelationService) {
298             // Trying to update a declared relation type
299             String excMsg = "Relation type cannot be updated as it is declared in the Relation Service.";
300             throw new RuntimeException(excMsg);
301         }
302 
303         String roleName = roleInfo.getName();
304 
305         // Checks if the role info has already been described
306         if (roleName2InfoMap.containsKey(roleName)) {
307             StringBuilder excMsgStrB = new StringBuilder();
308             String excMsg = "Two role infos provided for role ";
309             excMsgStrB.append(excMsg);
310             excMsgStrB.append(roleName);
311             throw new InvalidRelationTypeException(excMsgStrB.toString());
312         }
313 
314         roleName2InfoMap.put(roleName, new RoleInfo(roleInfo));
315 
316         RELATION_LOGGER.log(Level.TRACE, "RETURN");
317         return;
318     }
319 
320     // Sets the internal flag to specify that the relation type has been
321     // declared in the Relation Service
setRelationServiceFlag(boolean flag)322     void setRelationServiceFlag(boolean flag) {
323         isInRelationService = flag;
324         return;
325     }
326 
327     // Initializes the members, i.e. type name and role info list.
328     //
329     // -param relationTypeName  Name of relation type
330     // -param roleInfoArray  List of role definitions (RoleInfo objects)
331     //
332     // -exception IllegalArgumentException  if null parameter
333     // -exception InvalidRelationTypeException  If:
334     //  - the same name has been used for two different roles
335     //  - no role info provided
336     //  - one null role info provided
initMembers(String relationTypeName, RoleInfo[] roleInfoArray)337     private void initMembers(String relationTypeName,
338                              RoleInfo[] roleInfoArray)
339         throws IllegalArgumentException,
340                InvalidRelationTypeException {
341 
342         if (relationTypeName == null || roleInfoArray == null) {
343             String excMsg = "Invalid parameter.";
344             throw new IllegalArgumentException(excMsg);
345         }
346 
347         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
348 
349         typeName = relationTypeName;
350 
351         // Verifies role infos before setting them
352         // Can throw InvalidRelationTypeException
353         checkRoleInfos(roleInfoArray);
354 
355         for (int i = 0; i < roleInfoArray.length; i++) {
356             RoleInfo currRoleInfo = roleInfoArray[i];
357             roleName2InfoMap.put(currRoleInfo.getName(),
358                                  new RoleInfo(currRoleInfo));
359         }
360 
361         RELATION_LOGGER.log(Level.TRACE, "RETURN");
362         return;
363     }
364 
365     // Checks the given RoleInfo array to verify that:
366     // - the array is not empty
367     // - it does not contain a null element
368     // - a given role name is used only for one RoleInfo
369     //
370     // -param roleInfoArray  array to be checked
371     //
372     // -exception IllegalArgumentException
373     // -exception InvalidRelationTypeException  If:
374     //  - the same name has been used for two different roles
375     //  - no role info provided
376     //  - one null role info provided
checkRoleInfos(RoleInfo[] roleInfoArray)377     static void checkRoleInfos(RoleInfo[] roleInfoArray)
378         throws IllegalArgumentException,
379                InvalidRelationTypeException {
380 
381         if (roleInfoArray == null) {
382             String excMsg = "Invalid parameter.";
383             throw new IllegalArgumentException(excMsg);
384         }
385 
386         if (roleInfoArray.length == 0) {
387             // No role info provided
388             String excMsg = "No role info provided.";
389             throw new InvalidRelationTypeException(excMsg);
390         }
391 
392 
393         Set<String> roleNames = new HashSet<String>();
394 
395         for (int i = 0; i < roleInfoArray.length; i++) {
396             RoleInfo currRoleInfo = roleInfoArray[i];
397 
398             if (currRoleInfo == null) {
399                 String excMsg = "Null role info provided.";
400                 throw new InvalidRelationTypeException(excMsg);
401             }
402 
403             String roleName = currRoleInfo.getName();
404 
405             // Checks if the role info has already been described
406             if (roleNames.contains(roleName)) {
407                 StringBuilder excMsgStrB = new StringBuilder();
408                 String excMsg = "Two role infos provided for role ";
409                 excMsgStrB.append(excMsg);
410                 excMsgStrB.append(roleName);
411                 throw new InvalidRelationTypeException(excMsgStrB.toString());
412             }
413             roleNames.add(roleName);
414         }
415 
416         return;
417     }
418 
419 
420     /**
421      * Deserializes a {@link RelationTypeSupport} from an {@link ObjectInputStream}.
422      */
readObject(ObjectInputStream in)423     private void readObject(ObjectInputStream in)
424             throws IOException, ClassNotFoundException {
425       if (compat)
426       {
427         // Read an object serialized in the old serial form
428         //
429         ObjectInputStream.GetField fields = in.readFields();
430         typeName = (String) fields.get("myTypeName", null);
431         if (fields.defaulted("myTypeName"))
432         {
433           throw new NullPointerException("myTypeName");
434         }
435         roleName2InfoMap = cast(fields.get("myRoleName2InfoMap", null));
436         if (fields.defaulted("myRoleName2InfoMap"))
437         {
438           throw new NullPointerException("myRoleName2InfoMap");
439         }
440         isInRelationService = fields.get("myIsInRelServFlg", false);
441         if (fields.defaulted("myIsInRelServFlg"))
442         {
443           throw new NullPointerException("myIsInRelServFlg");
444         }
445       }
446       else
447       {
448         // Read an object serialized in the new serial form
449         //
450         in.defaultReadObject();
451       }
452     }
453 
454 
455     /**
456      * Serializes a {@link RelationTypeSupport} to an {@link ObjectOutputStream}.
457      */
writeObject(ObjectOutputStream out)458     private void writeObject(ObjectOutputStream out)
459             throws IOException {
460       if (compat)
461       {
462         // Serializes this instance in the old serial form
463         //
464         ObjectOutputStream.PutField fields = out.putFields();
465         fields.put("myTypeName", typeName);
466         fields.put("myRoleName2InfoMap", roleName2InfoMap);
467         fields.put("myIsInRelServFlg", isInRelationService);
468         out.writeFields();
469       }
470       else
471       {
472         // Serializes this instance in the new serial form
473         //
474         out.defaultWriteObject();
475       }
476     }
477 }
478