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 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             String form = AccessController.doPrivileged(act);
112             compat = (form != null && form.equals("1.0"));
113         } catch (Exception e) {
114             // OK : Too bad, no compat with 1.0
115         }
116         if (compat) {
117             serialPersistentFields = oldSerialPersistentFields;
118             serialVersionUID = oldSerialVersionUID;
119         } else {
120             serialPersistentFields = newSerialPersistentFields;
121             serialVersionUID = newSerialVersionUID;
122         }
123     }
124     //
125     // END Serialization compatibility stuff
126 
127     //
128     // Private members
129     //
130 
131     /**
132      * @serial Relation type name
133      */
134     private String typeName = null;
135 
136     /**
137      * @serial {@link Map} holding the mapping:
138      *           &lt;role name ({@link String})&gt; -&gt; &lt;role info ({@link RoleInfo} object)&gt;
139      */
140     private Map<String,RoleInfo> roleName2InfoMap =
141         new HashMap<String,RoleInfo>();
142 
143     /**
144      * @serial Flag specifying whether the relation type has been declared in the
145      *         Relation Service (so can no longer be updated)
146      */
147     private boolean isInRelationService = false;
148 
149     //
150     // Constructors
151     //
152 
153     /**
154      * Constructor where all role definitions are dynamically created and
155      * passed as parameter.
156      *
157      * @param relationTypeName  Name of relation type
158      * @param roleInfoArray  List of role definitions (RoleInfo objects)
159      *
160      * @exception IllegalArgumentException  if null parameter
161      * @exception InvalidRelationTypeException  if:
162      * <P>- the same name has been used for two different roles
163      * <P>- no role info provided
164      * <P>- one null role info provided
165      */
RelationTypeSupport(String relationTypeName, RoleInfo[] roleInfoArray)166     public RelationTypeSupport(String relationTypeName,
167                             RoleInfo[] roleInfoArray)
168         throws IllegalArgumentException,
169                InvalidRelationTypeException {
170 
171         if (relationTypeName == null || roleInfoArray == null) {
172             String excMsg = "Invalid parameter.";
173             throw new IllegalArgumentException(excMsg);
174         }
175 
176         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
177 
178         // Can throw InvalidRelationTypeException, ClassNotFoundException
179         // and NotCompliantMBeanException
180         initMembers(relationTypeName, roleInfoArray);
181 
182         RELATION_LOGGER.log(Level.TRACE, "RETURN");
183         return;
184     }
185 
186     /**
187      * Constructor to be used for subclasses.
188      *
189      * @param relationTypeName  Name of relation type.
190      *
191      * @exception IllegalArgumentException  if null parameter.
192      */
RelationTypeSupport(String relationTypeName)193     protected RelationTypeSupport(String relationTypeName)
194     {
195         if (relationTypeName == null) {
196             String excMsg = "Invalid parameter.";
197             throw new IllegalArgumentException(excMsg);
198         }
199 
200         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
201 
202         typeName = relationTypeName;
203 
204         RELATION_LOGGER.log(Level.TRACE, "RETURN");
205         return;
206     }
207 
208     //
209     // Accessors
210     //
211 
212     /**
213      * Returns the relation type name.
214      *
215      * @return the relation type name.
216      */
getRelationTypeName()217     public String getRelationTypeName() {
218         return typeName;
219     }
220 
221     /**
222      * Returns the list of role definitions (ArrayList of RoleInfo objects).
223      */
getRoleInfos()224     public List<RoleInfo> getRoleInfos() {
225         return new ArrayList<RoleInfo>(roleName2InfoMap.values());
226     }
227 
228     /**
229      * Returns the role info (RoleInfo object) for the given role info name
230      * (null if not found).
231      *
232      * @param roleInfoName  role info name
233      *
234      * @return RoleInfo object providing role definition
235      * does not exist
236      *
237      * @exception IllegalArgumentException  if null parameter
238      * @exception RoleInfoNotFoundException  if no role info with that name in
239      * relation type.
240      */
getRoleInfo(String roleInfoName)241     public RoleInfo getRoleInfo(String roleInfoName)
242         throws IllegalArgumentException,
243                RoleInfoNotFoundException {
244 
245         if (roleInfoName == null) {
246             String excMsg = "Invalid parameter.";
247             throw new IllegalArgumentException(excMsg);
248         }
249 
250         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", roleInfoName);
251 
252         // No null RoleInfo allowed, so use get()
253         RoleInfo result = roleName2InfoMap.get(roleInfoName);
254 
255         if (result == null) {
256             StringBuilder excMsgStrB = new StringBuilder();
257             String excMsg = "No role info for role ";
258             excMsgStrB.append(excMsg);
259             excMsgStrB.append(roleInfoName);
260             throw new RoleInfoNotFoundException(excMsgStrB.toString());
261         }
262 
263         RELATION_LOGGER.log(Level.TRACE, "RETURN");
264         return result;
265     }
266 
267     //
268     // Misc
269     //
270 
271     /**
272      * Add a role info.
273      * This method of course should not be used after the creation of the
274      * relation type, because updating it would invalidate that the relations
275      * created associated to that type still conform to it.
276      * Can throw a RuntimeException if trying to update a relation type
277      * declared in the Relation Service.
278      *
279      * @param roleInfo  role info to be added.
280      *
281      * @exception IllegalArgumentException  if null parameter.
282      * @exception InvalidRelationTypeException  if there is already a role
283      *  info in current relation type with the same name.
284      */
addRoleInfo(RoleInfo roleInfo)285     protected void addRoleInfo(RoleInfo roleInfo)
286         throws IllegalArgumentException,
287                InvalidRelationTypeException {
288 
289         if (roleInfo == null) {
290             String excMsg = "Invalid parameter.";
291             throw new IllegalArgumentException(excMsg);
292         }
293 
294         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", roleInfo);
295 
296         if (isInRelationService) {
297             // Trying to update a declared relation type
298             String excMsg = "Relation type cannot be updated as it is declared in the Relation Service.";
299             throw new RuntimeException(excMsg);
300         }
301 
302         String roleName = roleInfo.getName();
303 
304         // Checks if the role info has already been described
305         if (roleName2InfoMap.containsKey(roleName)) {
306             StringBuilder excMsgStrB = new StringBuilder();
307             String excMsg = "Two role infos provided for role ";
308             excMsgStrB.append(excMsg);
309             excMsgStrB.append(roleName);
310             throw new InvalidRelationTypeException(excMsgStrB.toString());
311         }
312 
313         roleName2InfoMap.put(roleName, new RoleInfo(roleInfo));
314 
315         RELATION_LOGGER.log(Level.TRACE, "RETURN");
316         return;
317     }
318 
319     // Sets the internal flag to specify that the relation type has been
320     // declared in the Relation Service
setRelationServiceFlag(boolean flag)321     void setRelationServiceFlag(boolean flag) {
322         isInRelationService = flag;
323         return;
324     }
325 
326     // Initializes the members, i.e. type name and role info list.
327     //
328     // -param relationTypeName  Name of relation type
329     // -param roleInfoArray  List of role definitions (RoleInfo objects)
330     //
331     // -exception IllegalArgumentException  if null parameter
332     // -exception InvalidRelationTypeException  If:
333     //  - the same name has been used for two different roles
334     //  - no role info provided
335     //  - one null role info provided
initMembers(String relationTypeName, RoleInfo[] roleInfoArray)336     private void initMembers(String relationTypeName,
337                              RoleInfo[] roleInfoArray)
338         throws IllegalArgumentException,
339                InvalidRelationTypeException {
340 
341         if (relationTypeName == null || roleInfoArray == null) {
342             String excMsg = "Invalid parameter.";
343             throw new IllegalArgumentException(excMsg);
344         }
345 
346         RELATION_LOGGER.log(Level.TRACE, "ENTRY {0}", relationTypeName);
347 
348         typeName = relationTypeName;
349 
350         // Verifies role infos before setting them
351         // Can throw InvalidRelationTypeException
352         checkRoleInfos(roleInfoArray);
353 
354         for (int i = 0; i < roleInfoArray.length; i++) {
355             RoleInfo currRoleInfo = roleInfoArray[i];
356             roleName2InfoMap.put(currRoleInfo.getName(),
357                                  new RoleInfo(currRoleInfo));
358         }
359 
360         RELATION_LOGGER.log(Level.TRACE, "RETURN");
361         return;
362     }
363 
364     // Checks the given RoleInfo array to verify that:
365     // - the array is not empty
366     // - it does not contain a null element
367     // - a given role name is used only for one RoleInfo
368     //
369     // -param roleInfoArray  array to be checked
370     //
371     // -exception IllegalArgumentException
372     // -exception InvalidRelationTypeException  If:
373     //  - the same name has been used for two different roles
374     //  - no role info provided
375     //  - one null role info provided
checkRoleInfos(RoleInfo[] roleInfoArray)376     static void checkRoleInfos(RoleInfo[] roleInfoArray)
377         throws IllegalArgumentException,
378                InvalidRelationTypeException {
379 
380         if (roleInfoArray == null) {
381             String excMsg = "Invalid parameter.";
382             throw new IllegalArgumentException(excMsg);
383         }
384 
385         if (roleInfoArray.length == 0) {
386             // No role info provided
387             String excMsg = "No role info provided.";
388             throw new InvalidRelationTypeException(excMsg);
389         }
390 
391 
392         Set<String> roleNames = new HashSet<String>();
393 
394         for (int i = 0; i < roleInfoArray.length; i++) {
395             RoleInfo currRoleInfo = roleInfoArray[i];
396 
397             if (currRoleInfo == null) {
398                 String excMsg = "Null role info provided.";
399                 throw new InvalidRelationTypeException(excMsg);
400             }
401 
402             String roleName = currRoleInfo.getName();
403 
404             // Checks if the role info has already been described
405             if (roleNames.contains(roleName)) {
406                 StringBuilder excMsgStrB = new StringBuilder();
407                 String excMsg = "Two role infos provided for role ";
408                 excMsgStrB.append(excMsg);
409                 excMsgStrB.append(roleName);
410                 throw new InvalidRelationTypeException(excMsgStrB.toString());
411             }
412             roleNames.add(roleName);
413         }
414 
415         return;
416     }
417 
418 
419     /**
420      * Deserializes a {@link RelationTypeSupport} from an {@link ObjectInputStream}.
421      */
readObject(ObjectInputStream in)422     private void readObject(ObjectInputStream in)
423             throws IOException, ClassNotFoundException {
424       if (compat)
425       {
426         // Read an object serialized in the old serial form
427         //
428         ObjectInputStream.GetField fields = in.readFields();
429         typeName = (String) fields.get("myTypeName", null);
430         if (fields.defaulted("myTypeName"))
431         {
432           throw new NullPointerException("myTypeName");
433         }
434         roleName2InfoMap = cast(fields.get("myRoleName2InfoMap", null));
435         if (fields.defaulted("myRoleName2InfoMap"))
436         {
437           throw new NullPointerException("myRoleName2InfoMap");
438         }
439         isInRelationService = fields.get("myIsInRelServFlg", false);
440         if (fields.defaulted("myIsInRelServFlg"))
441         {
442           throw new NullPointerException("myIsInRelServFlg");
443         }
444       }
445       else
446       {
447         // Read an object serialized in the new serial form
448         //
449         in.defaultReadObject();
450       }
451     }
452 
453 
454     /**
455      * Serializes a {@link RelationTypeSupport} to an {@link ObjectOutputStream}.
456      */
writeObject(ObjectOutputStream out)457     private void writeObject(ObjectOutputStream out)
458             throws IOException {
459       if (compat)
460       {
461         // Serializes this instance in the old serial form
462         //
463         ObjectOutputStream.PutField fields = out.putFields();
464         fields.put("myTypeName", typeName);
465         fields.put("myRoleName2InfoMap", roleName2InfoMap);
466         fields.put("myIsInRelServFlg", isInRelationService);
467         out.writeFields();
468       }
469       else
470       {
471         // Serializes this instance in the new serial form
472         //
473         out.defaultWriteObject();
474       }
475     }
476 }
477