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
26package javax.management.relation;
27
28import static com.sun.jmx.defaults.JmxProperties.RELATION_LOGGER;
29import static com.sun.jmx.mbeanserver.Util.cast;
30import com.sun.jmx.mbeanserver.GetPropertyAction;
31
32import java.io.IOException;
33import java.io.ObjectInputStream;
34import java.io.ObjectOutputStream;
35import java.io.ObjectStreamField;
36
37import java.security.AccessController;
38
39import java.util.ArrayList;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Map;
44import java.util.Set;
45import 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
67public 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     */
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     */
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     */
217    public String getRelationTypeName() {
218        return typeName;
219    }
220
221    /**
222     * Returns the list of role definitions (ArrayList of RoleInfo objects).
223     */
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     */
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     */
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
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
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
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     */
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     */
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