jdk/src/share/classes/com/sun/jmx/namespace/RoutingMBeanServerConnection.java
changeset 4156 acaa49a2768a
parent 4155 460e37d40f12
child 4159 9e3aae7675f1
equal deleted inserted replaced
4155:460e37d40f12 4156:acaa49a2768a
     1 /*
       
     2  * Copyright 2008 Sun Microsystems, Inc.  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.  Sun designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
       
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
       
    23  * have any questions.
       
    24  */
       
    25 
       
    26 package com.sun.jmx.namespace;
       
    27 
       
    28 import com.sun.jmx.defaults.JmxProperties;
       
    29 import com.sun.jmx.mbeanserver.Util;
       
    30 import java.io.IOException;
       
    31 import java.lang.reflect.UndeclaredThrowableException;
       
    32 import java.util.Set;
       
    33 import java.util.logging.Level;
       
    34 import java.util.logging.Logger;
       
    35 
       
    36 import javax.management.Attribute;
       
    37 import javax.management.AttributeList;
       
    38 import javax.management.AttributeNotFoundException;
       
    39 import javax.management.InstanceAlreadyExistsException;
       
    40 import javax.management.InstanceNotFoundException;
       
    41 import javax.management.IntrospectionException;
       
    42 import javax.management.InvalidAttributeValueException;
       
    43 import javax.management.JMRuntimeException;
       
    44 import javax.management.ListenerNotFoundException;
       
    45 import javax.management.MBeanException;
       
    46 import javax.management.MBeanInfo;
       
    47 import javax.management.MBeanRegistrationException;
       
    48 import javax.management.MBeanServerConnection;
       
    49 import javax.management.NotCompliantMBeanException;
       
    50 import javax.management.NotificationFilter;
       
    51 import javax.management.NotificationListener;
       
    52 import javax.management.ObjectInstance;
       
    53 import javax.management.ObjectName;
       
    54 import javax.management.QueryExp;
       
    55 import javax.management.ReflectionException;
       
    56 import javax.management.RuntimeMBeanException;
       
    57 import javax.management.RuntimeOperationsException;
       
    58 
       
    59 /**
       
    60  * A RoutingMBeanServerConnection wraps a MBeanServerConnection, defining
       
    61  * abstract methods that can be implemented by subclasses to rewrite
       
    62  * routing ObjectNames. It is used to implement
       
    63  * HandlerInterceptors (wrapping JMXNamespace instances) and routing
       
    64  * proxies (used to implement cd operations).
       
    65  * <p><b>
       
    66  * This API is a Sun internal API and is subject to changes without notice.
       
    67  * </b></p>
       
    68  * @since 1.7
       
    69  */
       
    70 public abstract class RoutingMBeanServerConnection<T extends MBeanServerConnection>
       
    71         implements MBeanServerConnection {
       
    72 
       
    73     /**
       
    74      * A logger for this class.
       
    75      **/
       
    76     private static final Logger LOG = JmxProperties.NAMESPACE_LOGGER;
       
    77 
       
    78     /**
       
    79      * Creates a new instance of RoutingMBeanServerConnection
       
    80      */
       
    81     public RoutingMBeanServerConnection() {
       
    82     }
       
    83 
       
    84     /**
       
    85      * Returns the wrapped source connection. The {@code source} connection
       
    86      * is a connection to the MBeanServer that contains the actual MBean.
       
    87      * In the case of cascading, that would be a connection to the sub
       
    88      * agent.
       
    89      **/
       
    90     protected abstract T source() throws IOException;
       
    91 
       
    92     /**
       
    93      * Converts a target ObjectName to a source ObjectName.
       
    94      * The target ObjectName is the name of the MBean in the mount point
       
    95      * target. In the case of cascading, that would be the name of the
       
    96      * MBean in the master agent. So if a subagent S containing an MBean
       
    97      * named "X" is mounted in the target namespace "foo//" of a master agent M,
       
    98      * the source is S, the target is "foo//" in M, the source name is "X", and
       
    99      * the target name is "foo//X".
       
   100      * In the case of cascading - such as in NamespaceInterceptor, this method
       
   101      * will convert "foo//X" (the targetName) into "X", the source name.
       
   102      * @throws IllegalArgumentException if the name cannot be converted.
       
   103      **/
       
   104     protected abstract ObjectName toSource(ObjectName targetName);
       
   105     /**
       
   106      * Converts a source ObjectName to a target ObjectName.
       
   107      * (see description of toSource above for explanations)
       
   108      * In the case of cascading - such as in NamespaceInterceptor, this method
       
   109      * will convert "X" (the sourceName) into "foo//X", the target name.
       
   110      * @throws IllegalArgumentException if the name cannot be converted.
       
   111      **/
       
   112     protected abstract ObjectName toTarget(ObjectName sourceName);
       
   113 
       
   114     /**
       
   115      * Can be overridden by subclasses to check the validity of a new
       
   116      * ObjectName used in createMBean or registerMBean.
       
   117      * This method is typically used by subclasses which might require
       
   118      * special handling for "null";
       
   119      **/
       
   120     protected ObjectName newSourceMBeanName(ObjectName targetName)
       
   121         throws MBeanRegistrationException {
       
   122         try {
       
   123             return toSource(targetName);
       
   124         } catch (Exception x) {
       
   125             throw new MBeanRegistrationException(x,"Illegal MBean Name");
       
   126         }
       
   127     }
       
   128 
       
   129     // Calls toSource(), Wraps IllegalArgumentException.
       
   130     ObjectName toSourceOrRuntime(ObjectName targetName) {
       
   131         try {
       
   132             return toSource(targetName);
       
   133         } catch (RuntimeException x) {
       
   134             throw makeCompliantRuntimeException(x);
       
   135         }
       
   136     }
       
   137 
       
   138 
       
   139     // Wraps given exception if needed.
       
   140     RuntimeException makeCompliantRuntimeException(Exception x) {
       
   141         if (x instanceof SecurityException)  return (SecurityException)x;
       
   142         if (x instanceof JMRuntimeException) return (JMRuntimeException)x;
       
   143         if (x instanceof RuntimeException)
       
   144             return new RuntimeOperationsException((RuntimeException)x);
       
   145         if (x instanceof IOException)
       
   146             return Util.newRuntimeIOException((IOException)x);
       
   147         // shouldn't come here...
       
   148         final RuntimeException x2 = new UndeclaredThrowableException(x);
       
   149         return new RuntimeOperationsException(x2);
       
   150     }
       
   151 
       
   152     // from MBeanServerConnection
       
   153     public AttributeList getAttributes(ObjectName name, String[] attributes)
       
   154         throws InstanceNotFoundException, ReflectionException, IOException {
       
   155         final ObjectName sourceName = toSourceOrRuntime(name);
       
   156         try {
       
   157             return source().getAttributes(sourceName, attributes);
       
   158         } catch (RuntimeException ex) {
       
   159             throw makeCompliantRuntimeException(ex);
       
   160         }
       
   161     }
       
   162 
       
   163     // from MBeanServerConnection
       
   164     public Object invoke(ObjectName name, String operationName, Object[] params,
       
   165                          String[] signature)
       
   166         throws InstanceNotFoundException, MBeanException, ReflectionException,
       
   167             IOException {
       
   168         final ObjectName sourceName = toSourceOrRuntime(name);
       
   169         try {
       
   170             final Object result =
       
   171                     source().invoke(sourceName,operationName,params,
       
   172                                    signature);
       
   173             return result;
       
   174         } catch (RuntimeException ex) {
       
   175             throw makeCompliantRuntimeException(ex);
       
   176         }
       
   177     }
       
   178 
       
   179     // from MBeanServerConnection
       
   180     public void unregisterMBean(ObjectName name)
       
   181         throws InstanceNotFoundException, MBeanRegistrationException,
       
   182             IOException {
       
   183         final ObjectName sourceName = toSourceOrRuntime(name);
       
   184         try {
       
   185             source().unregisterMBean(sourceName);
       
   186         } catch (RuntimeException ex) {
       
   187             throw makeCompliantRuntimeException(ex);
       
   188         }
       
   189     }
       
   190 
       
   191     // from MBeanServerConnection
       
   192     public MBeanInfo getMBeanInfo(ObjectName name)
       
   193         throws InstanceNotFoundException, IntrospectionException,
       
   194             ReflectionException, IOException {
       
   195         final ObjectName sourceName = toSourceOrRuntime(name);
       
   196         try {
       
   197             return source().getMBeanInfo(sourceName);
       
   198         } catch (RuntimeException ex) {
       
   199             throw makeCompliantRuntimeException(ex);
       
   200         }
       
   201     }
       
   202 
       
   203     // from MBeanServerConnection
       
   204     public ObjectInstance getObjectInstance(ObjectName name)
       
   205         throws InstanceNotFoundException, IOException {
       
   206         final ObjectName sourceName = toSourceOrRuntime(name);
       
   207         try {
       
   208             return processOutputInstance(
       
   209                     source().getObjectInstance(sourceName));
       
   210         } catch (RuntimeException ex) {
       
   211             throw makeCompliantRuntimeException(ex);
       
   212         }
       
   213     }
       
   214 
       
   215     // from MBeanServerConnection
       
   216     public boolean isRegistered(ObjectName name) throws IOException {
       
   217         final ObjectName sourceName = toSourceOrRuntime(name);
       
   218         try {
       
   219             return source().isRegistered(sourceName);
       
   220         } catch (RuntimeMBeanException x) {
       
   221             throw new RuntimeOperationsException(x.getTargetException());
       
   222         } catch (RuntimeException x) {
       
   223             throw makeCompliantRuntimeException(x);
       
   224         }
       
   225     }
       
   226 
       
   227     // from MBeanServerConnection
       
   228     public void setAttribute(ObjectName name, Attribute attribute)
       
   229         throws InstanceNotFoundException, AttributeNotFoundException,
       
   230             InvalidAttributeValueException, MBeanException,
       
   231             ReflectionException, IOException {
       
   232         final ObjectName sourceName = toSourceOrRuntime(name);
       
   233         try {
       
   234             source().setAttribute(sourceName,attribute);
       
   235         } catch (RuntimeException ex) {
       
   236             throw makeCompliantRuntimeException(ex);
       
   237         }
       
   238     }
       
   239 
       
   240     // from MBeanServerConnection
       
   241     public ObjectInstance createMBean(String className,
       
   242             ObjectName name, ObjectName loaderName,
       
   243             Object[] params, String[] signature)
       
   244         throws ReflectionException, InstanceAlreadyExistsException,
       
   245             MBeanRegistrationException, MBeanException,
       
   246             NotCompliantMBeanException, InstanceNotFoundException, IOException {
       
   247         final ObjectName sourceName = newSourceMBeanName(name);
       
   248         // Loader Name is already a sourceLoaderName.
       
   249         final ObjectName sourceLoaderName = loaderName;
       
   250         try {
       
   251             final ObjectInstance instance =
       
   252                     source().createMBean(className,sourceName,
       
   253                                          sourceLoaderName,
       
   254                                          params,signature);
       
   255             return processOutputInstance(instance);
       
   256         } catch (RuntimeException ex) {
       
   257             throw makeCompliantRuntimeException(ex);
       
   258         }
       
   259     }
       
   260 
       
   261     // from MBeanServerConnection
       
   262     public ObjectInstance createMBean(String className, ObjectName name,
       
   263             Object[] params, String[] signature)
       
   264             throws ReflectionException, InstanceAlreadyExistsException,
       
   265             MBeanRegistrationException, MBeanException,
       
   266             NotCompliantMBeanException, IOException {
       
   267         final ObjectName sourceName = newSourceMBeanName(name);
       
   268         try {
       
   269             return processOutputInstance(source().createMBean(className,
       
   270                     sourceName,params,signature));
       
   271         } catch (RuntimeException ex) {
       
   272             throw makeCompliantRuntimeException(ex);
       
   273         }
       
   274     }
       
   275 
       
   276     // from MBeanServerConnection
       
   277     public ObjectInstance createMBean(String className, ObjectName name,
       
   278             ObjectName loaderName)
       
   279             throws ReflectionException, InstanceAlreadyExistsException,
       
   280             MBeanRegistrationException, MBeanException,
       
   281             NotCompliantMBeanException, InstanceNotFoundException, IOException {
       
   282         final ObjectName sourceName = newSourceMBeanName(name);
       
   283         // Loader Name is already a source Loader Name.
       
   284         final ObjectName sourceLoaderName = loaderName;
       
   285         try {
       
   286             return processOutputInstance(source().createMBean(className,
       
   287                     sourceName,sourceLoaderName));
       
   288         } catch (RuntimeException ex) {
       
   289             throw makeCompliantRuntimeException(ex);
       
   290         }
       
   291     }
       
   292 
       
   293     // from MBeanServerConnection
       
   294     public ObjectInstance createMBean(String className, ObjectName name)
       
   295         throws ReflectionException, InstanceAlreadyExistsException,
       
   296             MBeanRegistrationException, MBeanException,
       
   297             NotCompliantMBeanException, IOException {
       
   298         final ObjectName sourceName = newSourceMBeanName(name);
       
   299         try {
       
   300             return processOutputInstance(source().
       
   301                     createMBean(className,sourceName));
       
   302         } catch (RuntimeException ex) {
       
   303             throw makeCompliantRuntimeException(ex);
       
   304         }
       
   305      }
       
   306 
       
   307     // from MBeanServerConnection
       
   308     public Object getAttribute(ObjectName name, String attribute)
       
   309         throws MBeanException, AttributeNotFoundException,
       
   310             InstanceNotFoundException, ReflectionException, IOException {
       
   311         final ObjectName sourceName = toSourceOrRuntime(name);
       
   312         try {
       
   313             return source().getAttribute(sourceName,attribute);
       
   314         } catch (RuntimeException ex) {
       
   315             throw makeCompliantRuntimeException(ex);
       
   316         }
       
   317     }
       
   318 
       
   319     // from MBeanServerConnection
       
   320     public boolean isInstanceOf(ObjectName name, String className)
       
   321         throws InstanceNotFoundException, IOException {
       
   322         final ObjectName sourceName = toSourceOrRuntime(name);
       
   323         try {
       
   324             return source().isInstanceOf(sourceName,className);
       
   325         } catch (RuntimeException ex) {
       
   326             throw makeCompliantRuntimeException(ex);
       
   327         }
       
   328     }
       
   329 
       
   330     // from MBeanServerConnection
       
   331     public AttributeList setAttributes(ObjectName name, AttributeList attributes)
       
   332         throws InstanceNotFoundException, ReflectionException, IOException {
       
   333         final ObjectName sourceName = toSourceOrRuntime(name);
       
   334         try {
       
   335             return source().
       
   336                     setAttributes(sourceName,attributes);
       
   337         } catch (RuntimeException ex) {
       
   338             throw makeCompliantRuntimeException(ex);
       
   339         }
       
   340     }
       
   341 
       
   342     // Return names in the target's context.
       
   343     Set<ObjectInstance> processOutputInstances(Set<ObjectInstance> sources) {
       
   344 
       
   345         final Set<ObjectInstance> result = Util.equivalentEmptySet(sources);
       
   346         for (ObjectInstance i : sources) {
       
   347             try {
       
   348                 final ObjectInstance target = processOutputInstance(i);
       
   349                 if (excludesFromResult(target.getObjectName(), "queryMBeans"))
       
   350                     continue;
       
   351                 result.add(target);
       
   352             } catch (Exception x) {
       
   353                 if (LOG.isLoggable(Level.FINE)) {
       
   354                     LOG.fine("Skiping returned item: " +
       
   355                              "Unexpected exception while processing " +
       
   356                              "ObjectInstance: " + x);
       
   357                 }
       
   358                 continue;
       
   359             }
       
   360         }
       
   361         return result;
       
   362     }
       
   363 
       
   364 
       
   365     // Return names in the target's context.
       
   366     ObjectInstance processOutputInstance(ObjectInstance source) {
       
   367         if (source == null) return null;
       
   368         final ObjectName sourceName = source.getObjectName();
       
   369         try {
       
   370             final ObjectName targetName = toTarget(sourceName);
       
   371             return new ObjectInstance(targetName,source.getClassName());
       
   372         } catch (RuntimeException x) {
       
   373             throw makeCompliantRuntimeException(x);
       
   374         }
       
   375     }
       
   376 
       
   377     // Returns names in the target's context.
       
   378     Set<ObjectName> processOutputNames(Set<ObjectName> sourceNames) {
       
   379 
       
   380         final Set<ObjectName> names = Util.equivalentEmptySet(sourceNames);
       
   381         for (ObjectName n : sourceNames) {
       
   382             try {
       
   383                 final ObjectName targetName = toTarget(n);
       
   384                 if (excludesFromResult(targetName, "queryNames")) continue;
       
   385                 names.add(targetName);
       
   386             } catch (Exception x) {
       
   387                 if (LOG.isLoggable(Level.FINE)) {
       
   388                     LOG.fine("Skiping returned item: " +
       
   389                              "Unexpected exception while processing " +
       
   390                              "ObjectInstance: " + x);
       
   391                 }
       
   392                 continue;
       
   393             }
       
   394         }
       
   395         return names;
       
   396     }
       
   397 
       
   398     // from MBeanServerConnection
       
   399     public Set<ObjectInstance> queryMBeans(ObjectName name,
       
   400             QueryExp query) throws IOException {
       
   401         if (name == null) name=ObjectName.WILDCARD;
       
   402         final ObjectName sourceName = toSourceOrRuntime(name);
       
   403         try {
       
   404             return processOutputInstances(
       
   405                     source().queryMBeans(sourceName,query));
       
   406         } catch (RuntimeException ex) {
       
   407             throw makeCompliantRuntimeException(ex);
       
   408         }
       
   409     }
       
   410 
       
   411     // from MBeanServerConnection
       
   412 
       
   413     public Set<ObjectName> queryNames(ObjectName name, QueryExp query)
       
   414         throws IOException {
       
   415         if (name == null) name=ObjectName.WILDCARD;
       
   416         final ObjectName sourceName = toSourceOrRuntime(name);
       
   417         try {
       
   418             final Set<ObjectName> tmp = source().queryNames(sourceName,query);
       
   419             final Set<ObjectName> out = processOutputNames(tmp);
       
   420             //System.err.println("queryNames: out: "+out);
       
   421             return out;
       
   422         } catch (RuntimeException ex) {
       
   423             throw makeCompliantRuntimeException(ex);
       
   424         }
       
   425     }
       
   426 
       
   427     // from MBeanServerConnection
       
   428     public void removeNotificationListener(ObjectName name,
       
   429             NotificationListener listener)
       
   430         throws InstanceNotFoundException,
       
   431         ListenerNotFoundException, IOException {
       
   432         final ObjectName sourceName = toSourceOrRuntime(name);
       
   433         try {
       
   434             source().removeNotificationListener(sourceName,listener);
       
   435         } catch (RuntimeException ex) {
       
   436             throw makeCompliantRuntimeException(ex);
       
   437         }
       
   438     }
       
   439 
       
   440     // from MBeanServerConnection
       
   441     public void addNotificationListener(ObjectName name, ObjectName listener,
       
   442             NotificationFilter filter, Object handback)
       
   443             throws InstanceNotFoundException, IOException {
       
   444         final ObjectName sourceName = toSourceOrRuntime(name);
       
   445         // Listener name is already a source listener name.
       
   446         try {
       
   447             source().addNotificationListener(sourceName,listener,
       
   448                     filter,handback);
       
   449         } catch (RuntimeException ex) {
       
   450             throw makeCompliantRuntimeException(ex);
       
   451         }
       
   452     }
       
   453 
       
   454     // from MBeanServerConnection
       
   455     public void addNotificationListener(ObjectName name,
       
   456                 NotificationListener listener, NotificationFilter filter,
       
   457                 Object handback) throws InstanceNotFoundException, IOException {
       
   458         final ObjectName sourceName = toSourceOrRuntime(name);
       
   459         try {
       
   460             source().addNotificationListener(sourceName, listener, filter,
       
   461                     handback);
       
   462         } catch (RuntimeException ex) {
       
   463             throw makeCompliantRuntimeException(ex);
       
   464         }
       
   465     }
       
   466 
       
   467 
       
   468     // from MBeanServerConnection
       
   469     public void removeNotificationListener(ObjectName name,
       
   470             NotificationListener listener, NotificationFilter filter,
       
   471             Object handback)
       
   472             throws InstanceNotFoundException, ListenerNotFoundException,
       
   473                 IOException {
       
   474         final ObjectName sourceName = toSourceOrRuntime(name);
       
   475         try {
       
   476             source().removeNotificationListener(sourceName,listener,filter,
       
   477                     handback);
       
   478         } catch (RuntimeException ex) {
       
   479             throw makeCompliantRuntimeException(ex);
       
   480         }
       
   481     }
       
   482 
       
   483     // from MBeanServerConnection
       
   484     public void removeNotificationListener(ObjectName name, ObjectName listener,
       
   485             NotificationFilter filter, Object handback)
       
   486             throws InstanceNotFoundException, ListenerNotFoundException,
       
   487             IOException {
       
   488         final ObjectName sourceName = toSourceOrRuntime(name);
       
   489         try {
       
   490             source().removeNotificationListener(sourceName,listener,
       
   491                     filter,handback);
       
   492         } catch (RuntimeException ex) {
       
   493             throw makeCompliantRuntimeException(ex);
       
   494         }
       
   495     }
       
   496 
       
   497     // from MBeanServerConnection
       
   498     public void removeNotificationListener(ObjectName name, ObjectName listener)
       
   499         throws InstanceNotFoundException, ListenerNotFoundException,
       
   500                IOException {
       
   501         final ObjectName sourceName = toSourceOrRuntime(name);
       
   502         // listener name is already a source name...
       
   503         final ObjectName sourceListener = listener;
       
   504         try {
       
   505             source().removeNotificationListener(sourceName,sourceListener);
       
   506         } catch (RuntimeException ex) {
       
   507             throw makeCompliantRuntimeException(ex);
       
   508         }
       
   509     }
       
   510 
       
   511     // from MBeanServerConnection
       
   512     public Integer getMBeanCount() throws IOException {
       
   513         try {
       
   514             return source().getMBeanCount();
       
   515         } catch (RuntimeException ex) {
       
   516             throw makeCompliantRuntimeException(ex);
       
   517         }
       
   518     }
       
   519 
       
   520     // from MBeanServerConnection
       
   521     public String[] getDomains() throws IOException {
       
   522         try {
       
   523             return source().getDomains();
       
   524         } catch (RuntimeException ex) {
       
   525             throw makeCompliantRuntimeException(ex);
       
   526         }
       
   527     }
       
   528 
       
   529     // from MBeanServerConnection
       
   530     public String getDefaultDomain() throws IOException {
       
   531         try {
       
   532             return source().getDefaultDomain();
       
   533         } catch (RuntimeException ex) {
       
   534             throw makeCompliantRuntimeException(ex);
       
   535         }
       
   536     }
       
   537 
       
   538     /**
       
   539      * Returns true if the given targetName must be excluded from the
       
   540      * query result.
       
   541      * In this base class, always return {@code false}.
       
   542      * By default all object names returned by the sources are
       
   543      * transmitted to the caller - there is no filtering.
       
   544      *
       
   545      * @param name         A target object name expressed in the caller's
       
   546      *                     context. In the case of cascading, where the source
       
   547      *                     is a sub agent mounted on e.g. namespace "foo",
       
   548      *                     that would be a name prefixed by "foo//"...
       
   549      * @param queryMethod  either "queryNames" or "queryMBeans".
       
   550      * @return true if the name must be excluded.
       
   551      */
       
   552     boolean excludesFromResult(ObjectName targetName, String queryMethod) {
       
   553         return false;
       
   554     }
       
   555 
       
   556 }