OpenPgpPubSubUtil.java

  1. /**
  2.  *
  3.  * Copyright 2018 Paul Schaub.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.ox.util;

  18. import java.lang.reflect.Constructor;
  19. import java.lang.reflect.Field;
  20. import java.lang.reflect.InvocationTargetException;
  21. import java.util.Date;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.logging.Level;
  25. import java.util.logging.Logger;

  26. import org.jivesoftware.smack.SmackException;
  27. import org.jivesoftware.smack.XMPPConnection;
  28. import org.jivesoftware.smack.XMPPException;
  29. import org.jivesoftware.smack.packet.StanzaError;

  30. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  31. import org.jivesoftware.smackx.ox.OpenPgpManager;
  32. import org.jivesoftware.smackx.ox.element.PubkeyElement;
  33. import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
  34. import org.jivesoftware.smackx.ox.element.SecretkeyElement;
  35. import org.jivesoftware.smackx.pep.PepManager;
  36. import org.jivesoftware.smackx.pubsub.AccessModel;
  37. import org.jivesoftware.smackx.pubsub.Item;
  38. import org.jivesoftware.smackx.pubsub.LeafNode;
  39. import org.jivesoftware.smackx.pubsub.Node;
  40. import org.jivesoftware.smackx.pubsub.PayloadItem;
  41. import org.jivesoftware.smackx.pubsub.PubSubException;
  42. import org.jivesoftware.smackx.pubsub.PubSubManager;
  43. import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
  44. import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;

  45. import org.jxmpp.jid.BareJid;
  46. import org.pgpainless.key.OpenPgpV4Fingerprint;

  47. public class OpenPgpPubSubUtil {

  48.     private static final Logger LOGGER = Logger.getLogger(OpenPgpPubSubUtil.class.getName());

  49.     /**
  50.      * Name of the OX metadata node.
  51.      *
  52.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey-list">XEP-0373 §4.2</a>
  53.      */
  54.     public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys";

  55.     /**
  56.      * Name of the OX secret key node.
  57.      */
  58.     public static final String PEP_NODE_SECRET_KEY = "urn:xmpp:openpgp:0:secret-key";

  59.     /**
  60.      * Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node.
  61.      *
  62.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#pubsub-notifications">XEP-0373 §4.4</a>
  63.      */
  64.     public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify";

  65.     /**
  66.      * Name of the OX public key node, which contains the key with id {@code id}.
  67.      *
  68.      * @param id upper case hex encoded OpenPGP v4 fingerprint of the key.
  69.      * @return PEP node name.
  70.      */
  71.     public static String PEP_NODE_PUBLIC_KEY(OpenPgpV4Fingerprint id) {
  72.         return PEP_NODE_PUBLIC_KEYS + ":" + id;
  73.     }

  74.     /**
  75.      * Query the access model of {@code node}. If it is different from {@code accessModel}, change the access model
  76.      * of the node to {@code accessModel}.
  77.      *
  78.      * @see <a href="https://xmpp.org/extensions/xep-0060.html#accessmodels">XEP-0060 §4.5 - Node Access Models</a>
  79.      *
  80.      * @param node {@link LeafNode} whose PubSub access model we want to change
  81.      * @param accessModel new access model.
  82.      *
  83.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  84.      * @throws SmackException.NotConnectedException if we are not connected.
  85.      * @throws InterruptedException if the thread is interrupted.
  86.      * @throws SmackException.NoResponseException if the server doesn't respond.
  87.      */
  88.     public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel)
  89.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  90.             SmackException.NoResponseException {
  91.         ConfigureForm current = node.getNodeConfiguration();
  92.         if (current.getAccessModel() != accessModel) {
  93.             FillableConfigureForm updateConfig = current.getFillableForm();
  94.             updateConfig.setAccessModel(accessModel);
  95.             node.sendConfigurationForm(updateConfig);
  96.         }
  97.     }

  98.     /**
  99.      * Publish the users OpenPGP public key to the public key node if necessary.
  100.      * Also announce the key to other users by updating the metadata node.
  101.      *
  102.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#announcing-pubkey">XEP-0373 §4.1</a>
  103.      *
  104.      * @param pepManager The PEP manager.
  105.      * @param pubkeyElement {@link PubkeyElement} containing the public key
  106.      * @param fingerprint fingerprint of the public key
  107.      *
  108.      * @throws InterruptedException if the thread gets interrupted.
  109.      * @throws PubSubException.NotALeafNodeException if either the metadata node or the public key node is not a
  110.      *                                               {@link LeafNode}.
  111.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  112.      * @throws SmackException.NotConnectedException if we are not connected.
  113.      * @throws SmackException.NoResponseException if the server doesn't respond.
  114.      */
  115.     public static void publishPublicKey(PepManager pepManager, PubkeyElement pubkeyElement, OpenPgpV4Fingerprint fingerprint)
  116.             throws InterruptedException, PubSubException.NotALeafNodeException,
  117.             XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException {

  118.         String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint);
  119.         PubSubManager pm = pepManager.getPepPubSubManager();

  120.         // Check if key available at data node
  121.         // If not, publish key to data node
  122.         LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName);
  123.         changeAccessModelIfNecessary(keyNode, AccessModel.open);
  124.         List<Item> items = keyNode.getItems(1);
  125.         if (items.isEmpty()) {
  126.             LOGGER.log(Level.FINE, "Node " + keyNodeName + " is empty. Publish.");
  127.             keyNode.publish(new PayloadItem<>(pubkeyElement));
  128.         } else {
  129.             LOGGER.log(Level.FINE, "Node " + keyNodeName + " already contains key. Skip.");
  130.         }

  131.         // Fetch IDs from metadata node
  132.         LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS);
  133.         changeAccessModelIfNecessary(metadataNode, AccessModel.open);
  134.         List<PayloadItem<PublicKeysListElement>> metadataItems = metadataNode.getItems(1);

  135.         PublicKeysListElement.Builder builder = PublicKeysListElement.builder();
  136.         if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) {
  137.             // Add old entries back to list.
  138.             PublicKeysListElement publishedList = metadataItems.get(0).getPayload();
  139.             for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) {
  140.                 builder.addMetadata(meta);
  141.             }
  142.         }
  143.         builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date()));

  144.         // Publish IDs to metadata node
  145.         metadataNode.publish(new PayloadItem<>(builder.build()));
  146.     }

  147.     /**
  148.      * Consult the public key metadata node and fetch a list of all of our published OpenPGP public keys.
  149.      *
  150.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey-list">
  151.      *      XEP-0373 §4.3: Discovering Public Keys of a User</a>
  152.      *
  153.      * @param connection XMPP connection
  154.      * @return content of our metadata node.
  155.      *
  156.      * @throws InterruptedException if the thread gets interrupted.
  157.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol exception.
  158.      * @throws PubSubException.NotAPubSubNodeException in case the queried entity is not a PubSub node
  159.      * @throws PubSubException.NotALeafNodeException in case the queried node is not a {@link LeafNode}
  160.      * @throws SmackException.NotConnectedException in case we are not connected
  161.      * @throws SmackException.NoResponseException in case the server doesn't respond
  162.      */
  163.     public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection)
  164.             throws InterruptedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException,
  165.             PubSubException.NotALeafNodeException, SmackException.NotConnectedException, SmackException.NoResponseException {
  166.         return fetchPubkeysList(connection, null);
  167.     }


  168.     /**
  169.      * Consult the public key metadata node of {@code contact} to fetch the list of their published OpenPGP public keys.
  170.      *
  171.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey-list">
  172.      *     XEP-0373 §4.3: Discovering Public Keys of a User</a>
  173.      *
  174.      * @param connection XMPP connection
  175.      * @param contact {@link BareJid} of the user we want to fetch the list from.
  176.      * @return content of {@code contact}'s metadata node.
  177.      *
  178.      * @throws InterruptedException if the thread gets interrupted.
  179.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol exception.
  180.      * @throws SmackException.NoResponseException in case the server doesn't respond
  181.      * @throws PubSubException.NotALeafNodeException in case the queried node is not a {@link LeafNode}
  182.      * @throws SmackException.NotConnectedException in case we are not connected
  183.      * @throws PubSubException.NotAPubSubNodeException in case the queried entity is not a PubSub node
  184.      */
  185.     public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection, BareJid contact)
  186.             throws InterruptedException, XMPPException.XMPPErrorException, SmackException.NoResponseException,
  187.             PubSubException.NotALeafNodeException, SmackException.NotConnectedException, PubSubException.NotAPubSubNodeException {
  188.         PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);

  189.         LeafNode node = getLeafNode(pm, PEP_NODE_PUBLIC_KEYS);
  190.         List<PayloadItem<PublicKeysListElement>> list = node.getItems(1);

  191.         if (list.isEmpty()) {
  192.             return null;
  193.         }

  194.         return list.get(0).getPayload();
  195.     }

  196.     /**
  197.      * Delete our metadata node.
  198.      *
  199.      * @param pepManager The PEP manager.
  200.      *
  201.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  202.      * @throws SmackException.NotConnectedException if we are not connected.
  203.      * @throws InterruptedException if the thread is interrupted.
  204.      * @throws SmackException.NoResponseException if the server doesn't respond.
  205.      * @return <code>true</code> if the node existed and was deleted, <code>false</code> if the node did not exist.
  206.      */
  207.     public static boolean deletePubkeysListNode(PepManager pepManager)
  208.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  209.             SmackException.NoResponseException {
  210.         PubSubManager pm = pepManager.getPepPubSubManager();
  211.         return pm.deleteNode(PEP_NODE_PUBLIC_KEYS);
  212.     }

  213.     /**
  214.      * Delete the public key node of the key with fingerprint {@code fingerprint}.
  215.      *
  216.      * @param pepManager The PEP manager.
  217.      * @param fingerprint fingerprint of the key we want to delete
  218.      *
  219.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  220.      * @throws SmackException.NotConnectedException if we are not connected.
  221.      * @throws InterruptedException if the thread gets interrupted.
  222.      * @throws SmackException.NoResponseException if the server doesn't respond.
  223.      * @return <code>true</code> if the node existed and was deleted, <code>false</code> if the node did not exist.
  224.      */
  225.     public static boolean deletePublicKeyNode(PepManager pepManager, OpenPgpV4Fingerprint fingerprint)
  226.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  227.             SmackException.NoResponseException {
  228.         PubSubManager pm = pepManager.getPepPubSubManager();
  229.         return pm.deleteNode(PEP_NODE_PUBLIC_KEY(fingerprint));
  230.     }


  231.     /**
  232.      * Fetch the OpenPGP public key of a {@code contact}, identified by its OpenPGP {@code v4_fingerprint}.
  233.      *
  234.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#discover-pubkey">XEP-0373 §4.3</a>
  235.      *
  236.      * @param connection XMPP connection
  237.      * @param contact {@link BareJid} of the contact we want to fetch a key from.
  238.      * @param v4_fingerprint upper case, hex encoded v4 fingerprint of the contacts key.
  239.      * @return {@link PubkeyElement} containing the requested public key.
  240.      *
  241.      * @throws InterruptedException if the thread gets interrupted.A
  242.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  243.      * @throws PubSubException.NotAPubSubNodeException in case the targeted entity is not a PubSub node.
  244.      * @throws PubSubException.NotALeafNodeException in case the fetched node is not a {@link LeafNode}.
  245.      * @throws SmackException.NotConnectedException in case we are not connected.
  246.      * @throws SmackException.NoResponseException if the server doesn't respond.
  247.      */
  248.     public static PubkeyElement fetchPubkey(XMPPConnection connection, BareJid contact, OpenPgpV4Fingerprint v4_fingerprint)
  249.             throws InterruptedException, XMPPException.XMPPErrorException, PubSubException.NotAPubSubNodeException,
  250.             PubSubException.NotALeafNodeException, SmackException.NotConnectedException, SmackException.NoResponseException {
  251.         PubSubManager pm = PubSubManager.getInstanceFor(connection, contact);
  252.         String nodeName = PEP_NODE_PUBLIC_KEY(v4_fingerprint);

  253.         LeafNode node = getLeafNode(pm, nodeName);

  254.         List<PayloadItem<PubkeyElement>> list = node.getItems(1);

  255.         if (list.isEmpty()) {
  256.             return null;
  257.         }

  258.         return list.get(0).getPayload();
  259.     }

  260.     /**
  261.      * Try to get a {@link LeafNode} the traditional way (first query information using disco#info), then query the node.
  262.      * If that fails, query the node directly.
  263.      *
  264.      * @param pm PubSubManager
  265.      * @param nodeName name of the node
  266.      * @return node TODO javadoc me please
  267.      *
  268.      * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error.
  269.      * @throws PubSubException.NotALeafNodeException if the queried node is not a {@link LeafNode}.
  270.      * @throws InterruptedException in case the thread gets interrupted
  271.      * @throws PubSubException.NotAPubSubNodeException in case the queried entity is not a PubSub node.
  272.      * @throws SmackException.NotConnectedException in case the connection is not connected.
  273.      * @throws SmackException.NoResponseException in case the server doesn't respond.
  274.      */
  275.     static LeafNode getLeafNode(PubSubManager pm, String nodeName)
  276.             throws XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, InterruptedException,
  277.             PubSubException.NotAPubSubNodeException, SmackException.NotConnectedException, SmackException.NoResponseException {
  278.         LeafNode node;
  279.         try {
  280.             node = pm.getLeafNode(nodeName);
  281.         } catch (XMPPException.XMPPErrorException e) {
  282.             // It might happen, that the server doesn't allow disco#info queries from strangers.
  283.             // In that case we have to fetch the node directly
  284.             if (e.getStanzaError().getCondition() == StanzaError.Condition.subscription_required) {
  285.                 node = getOpenLeafNode(pm, nodeName);
  286.             } else {
  287.                 throw e;
  288.             }
  289.         }

  290.         return node;
  291.     }

  292.     /**
  293.      * Publishes a {@link SecretkeyElement} to the secret key node.
  294.      * The node will be configured to use the whitelist access model to prevent access from subscribers.
  295.      *
  296.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">
  297.      *     XEP-0373 §5. Synchronizing the Secret Key with a Private PEP Node</a>
  298.      *
  299.      * @param connection {@link XMPPConnection} of the user
  300.      * @param element a {@link SecretkeyElement} containing the encrypted secret key of the user
  301.      *
  302.      * @throws InterruptedException if the thread gets interrupted.
  303.      * @throws PubSubException.NotALeafNodeException if something is wrong with the PubSub node
  304.      * @throws XMPPException.XMPPErrorException in case of an protocol related error
  305.      * @throws SmackException.NotConnectedException if we are not connected
  306.      * @throws SmackException.NoResponseException /watch?v=0peBq89ZTrc
  307.      * @throws SmackException.FeatureNotSupportedException if the Server doesn't support the whitelist access model
  308.      */
  309.     public static void depositSecretKey(XMPPConnection connection, SecretkeyElement element)
  310.             throws InterruptedException, PubSubException.NotALeafNodeException,
  311.             XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException,
  312.             SmackException.FeatureNotSupportedException {
  313.         if (!OpenPgpManager.serverSupportsSecretKeyBackups(connection)) {
  314.             throw new SmackException.FeatureNotSupportedException("http://jabber.org/protocol/pubsub#access-whitelist");
  315.         }
  316.         PubSubManager pm = PepManager.getInstanceFor(connection).getPepPubSubManager();
  317.         LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
  318.         OpenPgpPubSubUtil.changeAccessModelIfNecessary(secretKeyNode, AccessModel.whitelist);

  319.         secretKeyNode.publish(new PayloadItem<>(element));
  320.     }

  321.     /**
  322.      * Fetch the latest {@link SecretkeyElement} from the private backup node.
  323.      *
  324.      * @see <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">
  325.      *      XEP-0373 §5. Synchronizing the Secret Key with a Private PEP Node</a>
  326.      *
  327.      * @param pepManager the PEP manager.
  328.      * @return the secret key node or null, if it doesn't exist.
  329.      *
  330.      * @throws InterruptedException if the thread gets interrupted
  331.      * @throws PubSubException.NotALeafNodeException if there is an issue with the PubSub node
  332.      * @throws XMPPException.XMPPErrorException if there is an XMPP protocol related issue
  333.      * @throws SmackException.NotConnectedException if we are not connected
  334.      * @throws SmackException.NoResponseException /watch?v=7U0FzQzJzyI
  335.      */
  336.     public static SecretkeyElement fetchSecretKey(PepManager pepManager)
  337.             throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
  338.             SmackException.NotConnectedException, SmackException.NoResponseException {
  339.         PubSubManager pm = pepManager.getPepPubSubManager();
  340.         LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY);
  341.         List<PayloadItem<SecretkeyElement>> list = secretKeyNode.getItems(1);
  342.         if (list.size() == 0) {
  343.             LOGGER.log(Level.INFO, "No secret key published!");
  344.             return null;
  345.         }
  346.         SecretkeyElement secretkeyElement = list.get(0).getPayload();
  347.         return secretkeyElement;
  348.     }

  349.     /**
  350.      * Delete the private backup node.
  351.      *
  352.      * @param pepManager the PEP manager.
  353.      *
  354.      * @throws XMPPException.XMPPErrorException if there is an XMPP protocol related issue
  355.      * @throws SmackException.NotConnectedException if we are not connected
  356.      * @throws InterruptedException if the thread gets interrupted
  357.      * @throws SmackException.NoResponseException if the server sends no response
  358.      * @return <code>true</code> if the node existed and was deleted, <code>false</code> if the node did not exist.
  359.      */
  360.     public static boolean deleteSecretKeyNode(PepManager pepManager)
  361.             throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException,
  362.             SmackException.NoResponseException {
  363.         PubSubManager pm = pepManager.getPepPubSubManager();
  364.         return pm.deleteNode(PEP_NODE_SECRET_KEY);
  365.     }

  366.     /**
  367.      * Use reflection magic to get a {@link LeafNode} without doing a disco#info query.
  368.      * This method is useful for fetching nodes that are configured with the access model 'open', since
  369.      * some servers that announce support for that access model do not allow disco#info queries from contacts
  370.      * which are not subscribed to the node owner. Therefore this method fetches the node directly and puts it
  371.      * into the {@link PubSubManager}s node map.
  372.      *
  373.      * Note: Due to the lack of a disco#info query, it might happen, that the node doesn't exist on the server,
  374.      * even though we add it to the node map.
  375.      *
  376.      * @see <a href="https://github.com/processone/ejabberd/issues/2483">Ejabberd bug tracker about the issue</a>
  377.      * @see <a href="https://mail.jabber.org/pipermail/standards/2018-June/035206.html">
  378.      *     Topic on the standards mailing list</a>
  379.      *
  380.      * @param pubSubManager pubsub manager
  381.      * @param nodeName name of the node
  382.      * @return leafNode TODO javadoc me please
  383.      *
  384.      * @throws PubSubException.NotALeafNodeException in case we already have the node cached, but it is not a LeafNode.
  385.      */
  386.     @SuppressWarnings("unchecked")
  387.     public static LeafNode getOpenLeafNode(PubSubManager pubSubManager, String nodeName)
  388.             throws PubSubException.NotALeafNodeException {

  389.         try {

  390.             // Get access to the PubSubManager's nodeMap
  391.             Field field = pubSubManager.getClass().getDeclaredField("nodeMap");
  392.             field.setAccessible(true);
  393.             Map<String, Node> nodeMap = (Map<String, Node>) field.get(pubSubManager);

  394.             // Check, if the node already exists
  395.             Node existingNode = nodeMap.get(nodeName);
  396.             if (existingNode != null) {

  397.                 if (existingNode instanceof LeafNode) {
  398.                     // We already know that node
  399.                     return (LeafNode) existingNode;

  400.                 } else {
  401.                     // Throw a new NotALeafNodeException, as the node is not a LeafNode.
  402.                     // Again use reflections to access the exceptions constructor.
  403.                     Constructor<PubSubException.NotALeafNodeException> exceptionConstructor =
  404.                             PubSubException.NotALeafNodeException.class.getDeclaredConstructor(String.class, BareJid.class);
  405.                     exceptionConstructor.setAccessible(true);
  406.                     throw exceptionConstructor.newInstance(nodeName, pubSubManager.getServiceJid());
  407.                 }
  408.             }

  409.             // Node does not exist. Create the node
  410.             Constructor<LeafNode> constructor;
  411.             constructor = LeafNode.class.getDeclaredConstructor(PubSubManager.class, String.class);
  412.             constructor.setAccessible(true);
  413.             LeafNode node = constructor.newInstance(pubSubManager, nodeName);

  414.             // Add it to the node map
  415.             nodeMap.put(nodeName, node);

  416.             // And return
  417.             return node;

  418.         } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException |
  419.                 NoSuchFieldException e) {
  420.             LOGGER.log(Level.SEVERE, "Using reflections to create a LeafNode and put it into PubSubManagers nodeMap failed.", e);
  421.             throw new AssertionError(e);
  422.         }
  423.     }
  424. }