/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.start;
import com.sun.jini.config.Config;
import net.jini.config.Configuration;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationGroupID;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationSystem;
import java.rmi.MarshalledObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class used to launch shared, activatable services.
* Clients construct this object with the details
* of the service to be launched, then call
* {@link #create(net.jini.config.Configuration) create(Configuration config) }
* to launch the service in an existing
* {@linkplain SharedActivationGroupDescriptor shared activation group}
* identified by the <code>sharedGroupLog</code> constructor parameter.
* <P>
* This class depends on {@link ActivateWrapper} to provide
* separation of the import codebase
* (where the service implementation classes are loaded from)
* from the export codebase
* (where service clients should load classes from, for example stubs)
* as well as providing an independent security policy file for each
* service object. This functionality allows multiple service objects
* to be placed in the same activation system group, with each object
* maintaining distinct export codebase and security policy settings.
* <P>
* Services need to implement the "activatable"
* <A HREF="ActivateWrapper.html#serviceConstructor">constructor</A>
* required by {@link ActivateWrapper}.
* <P>
* <A NAME="serviceProxy"></A>
* A service implementation
* can return its service proxy (via the <code>proxy</code> field of the
* {@link Created Created} object returned
* by {@link #create(net.jini.config.Configuration) create})
* in the following ways:
* <DL>
* <DT><A NAME="innerProxy">Return Inner Proxy</A></DT>
* <DD><P>
* The service's inner proxy is the
* {@link java.rmi.Remote} object returned from
* {@link java.rmi.activation.ActivationID#activate(boolean)} using
* {@link ActivateWrapper} to "wrap" and register the desired service
* with the activation system.
* A "wrapped" service's inner proxy is returned as follows:
*
* <UL>
* <LI>If the newly created service instance implements {@link
* net.jini.export.ProxyAccessor}, a proxy is obtained by invoking the {@link
* net.jini.export.ProxyAccessor#getProxy getProxy} method on that instance. If the
* obtained proxy is not <code>null</code>, that proxy is returned in a
* {@link java.rmi.MarshalledObject}; otherwise, an
* {@link java.io.InvalidObjectException} is thrown.
*
* <LI>If the newly created instance does not implement
* {@link net.jini.export.ProxyAccessor}, the instance is returned in a
* {@link java.rmi.MarshalledObject}. In this case, the instance must be
* serializable, and marshalling the instance must produce a suitable
* proxy for the remote object (for example, the object implements
* {@link java.io.Serializable} and defines a <code>writeReplace</code>
* method that returns the object's proxy).
* </UL>
*
* <P></DD>
* <DT><A NAME="outerProxy">Return Outer Proxy</A></DT>
* <DD><P>
* The service's outer proxy is the object returned from invoking
* {@link ServiceProxyAccessor#getServiceProxy()} on
* the service's <A HREF="#innerProxy">inner proxy</A>.
* <P></DD>
* </DL>
*
* The following items are discussed below:
* <ul>
* <li><a href="#configEntries">Configuring SharedActivatableServiceDescriptor</a>
* <li><a href="#logging">Logging</a>
* </ul>
*
* <a name="configEntries">
* <h3>Configuring SharedActivatableServiceDescriptor</h3>
* </a>
*
* <code>SharedActivatableServiceDescriptor</code>
* depends on {@link ActivateWrapper}, which can itself be configured. See
* {@linkplain ActivateWrapper}'s
* <A HREF="ActivateWrapper.html#configEntries">configuration</A>
* information for details.
*<p>
* This implementation obtains its configuration entries from the
* {@link net.jini.config.Configuration Configuration} object passed into
* the {@link #create(net.jini.config.Configuration) create} method.
* The following configuration entries use the
* component prefix "<code>com.sun.jini.start</code>":
* <p>
*
* <table summary="Describes the activationIdPreparer configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1"><code>
* activationIdPreparer</code></font>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>
* new {@link net.jini.security.BasicProxyPreparer}()
* </code>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description: <td> The proxy preparer for the service's activation
* ID. The value should not be <code>null</code>.
*
* This class calls the
* {@link java.rmi.activation.ActivationID#activate
* activate} method on instances of {@link
* java.rmi.activation.ActivationID} when they need to re/activate the
* service.
* </table>
*
* <table summary="Describes the activationSystemPreparer configuration
* entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1"><code>
* activationSystemPreparer</code></font>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>
* new {@link net.jini.security.BasicProxyPreparer}()</code>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description: <td> The proxy preparer for the proxy for the
* activation system. The value should not be <code>null</code>.
*
* The service starter calls the {@link
* java.rmi.activation.ActivationSystem#unregisterObject
* unregisterObject} method on the {@link
* java.rmi.activation.ActivationSystem} when there is a problem
* creating a service.
* </table>
*
* <a name="servicePreparer">
* <table summary="Describes the default service preparer configuration entry"
* border="0" cellpadding="2">
* <tr valign="top">
* <th scope="col" summary="layout"> <font size="+1">•</font>
* <th scope="col" align="left" colspan="2"> <font size="+1"><code>
* servicePreparer</code></font>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Type: <td> {@link net.jini.security.ProxyPreparer}
* <tr valign="top"> <td>   <th scope="row" align="right">
* Default: <td> <code>
* new {@link net.jini.security.BasicProxyPreparer}()
* </code>
* <tr valign="top"> <td>   <th scope="row" align="right">
* Description: <td> The default proxy preparer used to prepare
* service proxies.
* This value should not be <code>null</code>. This entry is
* obtained during the invocation of
* {@link #create(net.jini.config.Configuration) create} and is used,
* to prepare the <A HREF="#innerProxy">inner</A> and
* <A HREF="#outerProxy">outer</A> service proxies returned by
* the service implementation
* (see <a href="#serviceProxy">service proxy</a> section for details).
* This entry is superseded by explicitly passing a
* {@link ProxyPreparer ProxyPreparer} to one of the constructors that
* accept a {@linkplain ProxyPreparer proxy preparer} argument.
* </table>
*
*<a name="logging">
*<h3>Loggers and Logging Levels</h3>
*</a>
*
* The implementation uses the {@link
* java.util.logging.Logger}, named
* <code>com.sun.jini.start.service.starter</code>.
* The following table describes the
* type of information logged as well as the levels of information logged.
* <p>
*
* <table border="1" cellpadding="5"
* summary="Describes logging performed by the shared,
* activatable service descriptor at different
* logging levels">
*
* <caption halign="center" valign="top"><b><code>
* com.sun.jini.start.service.starter</code></b></caption>
*
* <tr> <th scope="col"> Level <th scope="col"> Description
*
* <tr> <td> {@link java.util.logging.Level#FINER FINER} <td>
* for high level
* service operation tracing
* <tr> <td> {@link java.util.logging.Level#FINEST FINEST} <td>
* for low level
* service operation tracing
*
* </table> <p>
*
* @author Sun Microsystems, Inc.
* @since 2.0
*
*/
public class SharedActivatableServiceDescriptor
extends NonActivatableServiceDescriptor
{
private static final long serialVersionUID = 1L;
/**
* @serial <code>String</code> representing the directory where
* the associated shared group identifier information is persisted
*/
private final String sharedGroupLog;
/**
* @serial <code>boolean</code> flag passed through as the
* <code>restart</code> parameter to the
* {@linkplain java.rmi.activation.ActivationDesc#ActivationDesc(java.rmi.activation.ActivationGroupID, java.lang.String, java.lang.String, java.rmi.MarshalledObject, boolean)
* ActivationDesc constructor} used to register the service with the
* activation system.
*/
private final boolean restart;
/**
* @serial <code>String</code> containing hostname of desired
* activation system.
*/
private final String host;
/**
* @serial <code>int</code> representing port of desired activation system.
*/
private final int port;
private static final Logger logger = ServiceStarter.logger;
/*
* <code>ProxyPreparer</code> reference for preparing the service's proxy
* returned from the call to
* {@linkplain java.rmi.activation.ActivationID#activate(boolean) activate}.
* If this "inner" proxy implements
* {@linkplain ServiceProxyAccessor ServiceProxyAccessor} it will then be used to invoke
* {@linkplain ServiceProxyAccessor#getServiceProxy() getServiceProxy}.
*/
private /*final*/ transient ProxyPreparer innerProxyPreparer;
/**
* Object returned by
* {@link SharedActivatableServiceDescriptor#create(net.jini.config.Configuration)
* SharedActivatableServiceDescriptor.create()}
* method that returns the associated proxy, activation group
* identifier, and activation identifier
* for the created service.
*/
public static class Created {
/** The activation group id of the group hosting the service */
public final ActivationGroupID gid;
/** The activation id of the service */
public final ActivationID aid;
/** The reference to the proxy of the created service */
public final Object proxy;
/** Constructs an instance of this class.
* @param gid activation group id of the created service
* @param aid activation id of the created service
* @param proxy reference to the proxy of the created service
*/
public Created(ActivationGroupID gid, ActivationID aid, Object proxy) {
this.gid = gid;
this.aid = aid;
this.proxy = proxy;
}//end constructor
}//end class Created
/**
* Convenience constructor. Equivalent to calling this
* {@link #SharedActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], net.jini.security.ProxyPreparer, net.jini.security.ProxyPreparer, boolean, java.lang.String, int) constructor}
* with the <code>innerProxyPreparer</code>, <code>outerProxyPreparer</code>
* and <code>host</code> arguments set to
* <code>null</code>
* and the <code>port</code> argument set to
* the currently configured activation system port.
* The activation system port defaults to
* {@link ActivationSystem#SYSTEM_PORT} unless it is overridden by the
* <code>java.rmi.activation.port</code> system property.
*
*/
public SharedActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
String sharedGroupLog,
// Optional Args,
String[] serverConfigArgs,
boolean restart)
{
this(exportCodebase, policy, importCodebase, implClassName,
sharedGroupLog, serverConfigArgs, null,
null, restart, null, ServiceStarter.getActivationSystemPort());
}
/**
* Convenience constructor. Equivalent to calling this
* {@link #SharedActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], net.jini.security.ProxyPreparer, net.jini.security.ProxyPreparer, boolean, java.lang.String, int) constructor}
* with the <code>host</code> argument set to
* <code>null</code> and the <code>port</code> argument set to the
* currently configured activation system port.
* The activation system port defaults to
* {@link ActivationSystem#SYSTEM_PORT} unless it is overridden by the
* <code>java.rmi.activation.port</code> system property.
*
*/
public SharedActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
String sharedGroupLog,
// Optional Args,
String[] serverConfigArgs,
ProxyPreparer innerProxyPreparer,
ProxyPreparer outerProxyPreparer,
boolean restart)
{
this(exportCodebase, policy, importCodebase, implClassName,
sharedGroupLog, serverConfigArgs, innerProxyPreparer,
outerProxyPreparer, restart, null, ServiceStarter.getActivationSystemPort());
}
/**
* Convenience constructor. Equivalent to calling this
* {@link #SharedActivatableServiceDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[], net.jini.security.ProxyPreparer, net.jini.security.ProxyPreparer, boolean, java.lang.String, int) constructor}
* with the <code>innerProxyPreparer</code> and <code>outerProxyPreparer</code> arguments set to
* <code>null</code>.
*/
public SharedActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
String sharedGroupLog,
// Optional Args,
String[] serverConfigArgs,
boolean restart,
String host,
int port)
{
this(exportCodebase, policy, importCodebase, implClassName,
sharedGroupLog, serverConfigArgs, null, null, restart,
host, port);
}
/**
* Main constructor. Simply assigns given parameters to
* their associated, internal fields unless otherwise noted.
*
* @param exportCodebase location where clients can download required
* service-related classes (for example, stubs, proxies, etc.).
* Codebase components must be separated by spaces in which
* each component is in <code>URL</code> format.
* @param policy server policy filename or URL
* @param importCodebase location where server implementation
* classes can be found.
* This <code>String</code> assumed (in order) to be either
* 1) a space delimited set of <code>URL</code>(s)
* representing a codebase or
* 2) a <code>File.pathSeparator</code> delimited set
* of class paths.
* @param implClassName name of server implementation class
* @param sharedGroupLog location where the associated shared
* group identifier information is persisted
* @param serverConfigArgs service configuration arguments
* @param innerProxyPreparer <code>ProxyPreparer</code> reference. This object
* will be used to prepare the service's <A HREF="#innerProxy">inner proxy</A>.
* If the inner proxy implements
* {@link ServiceProxyAccessor}
* it will then be used to invoke
* {@link ServiceProxyAccessor#getServiceProxy()} in order to get the
* service's <A HREF="#outerProxy">outer proxy</A>.
* @param outerProxyPreparer <code>ProxyPreparer</code> reference. This object
* will be used to prepare the service's
* <A HREF="#outerProxy">outer proxy</A> before it is returned to the caller of
* {@link SharedActivatableServiceDescriptor#create(net.jini.config.Configuration)}.
* @param restart boolean flag passed through as the
* <code>restart</code> parameter to the
* {@linkplain java.rmi.activation.ActivationDesc#ActivationDesc(java.rmi.activation.ActivationGroupID, java.lang.String, java.lang.String, java.rmi.MarshalledObject, boolean)
* ActivationDesc constructor} used to register the service with the
* activation system.
* @param host hostname of desired activation system. If <code>null</code>,
* defaults to the localhost.
* @param port port of desired activation system. If value is <= 0, then
* defaults to
* {@link java.rmi.activation.ActivationSystem#SYSTEM_PORT
* ActivationSystem.SYSTEM_PORT}.
*/
public SharedActivatableServiceDescriptor(
// Required Args
String exportCodebase,
String policy,
String importCodebase,
String implClassName,
String sharedGroupLog,
// Optional Args,
String[] serverConfigArgs,
ProxyPreparer innerProxyPreparer,
ProxyPreparer outerProxyPreparer,
boolean restart,
String host,
int port)
{
super(exportCodebase, policy, importCodebase, implClassName,
serverConfigArgs, outerProxyPreparer);
if ( sharedGroupLog == null)
throw new NullPointerException(
"Shared VM log cannot be null");
this.sharedGroupLog = sharedGroupLog;
this.restart = restart;
this.host = (host == null) ? "" : host;
this.port =
(port <= 0) ? ServiceStarter.getActivationSystemPort() : port;
this.innerProxyPreparer = innerProxyPreparer;
}
/**
* Shared group log accessor method.
*
* @return the Shared group log associated with this service descriptor.
*/
final public String getSharedGroupLog() { return sharedGroupLog; }
/**
* Restart accessor method.
*
* @return the restart mode associated with this service descriptor.
*/
final public boolean getRestart() { return restart; }
/**
* Activation system host accessor method.
*
* @return the activation system host associated with
* this service descriptor.
*/
final public String getActivationSystemHost() { return host; }
/**
* Activation system port accessor method.
*
* @return the activation system port associated with this service descriptor.
*/
final public int getActivationSystemPort() { return port; }
/**
* <code>ProxyPreparer</code> accessor method.
*
* @return the <A HREF="#innerProxy">inner proxy</A> preparer associated with
* this service descriptor.
*/
final public ProxyPreparer getInnerProxyPreparer() { return innerProxyPreparer; }
/**
* Sets the <A HREF="#innerProxy">inner</A>
* <code>ProxyPreparer</code> for this descriptor.
* This needs to
* be called on the service descriptor prior to calling
* <code>create()</code>. Useful for (re-)setting the
* the associated inner <code>ProxyPreparer</code> upon deserialization of
* the descriptor.
*
* @param pp The inner <code>ProxyPreparer</code> object to be
* associated with this service descriptor.
*
* @throws IllegalStateException if called after <code>create()</code> is invoked
*/
final public void setInnerProxyPreparer(ProxyPreparer pp) {
synchronized (descCreatedLock) {
if (descCreated) {
throw new IllegalStateException("Can't set ProxyPreparer after descriptor creation");
} else {
innerProxyPreparer = pp;
}
}
}
/**
* Method that attempts to create a service based on the service
* description information provided via constructor parameters.
* <P>
* This method:
* <UL>
* <LI> creates an <code>ActivateWrapper.ActivateDesc</code> with
* the provided constructor parameter information for the
* desired service
* <LI> retrieves the
* {@linkplain java.rmi.activation.ActivationGroupID group identifier}
* associated with the provided shared group log.
* <LI> invokes
* {@link ActivateWrapper#register(java.rmi.activation.ActivationGroupID, com.sun.jini.start.ActivateWrapper.ActivateDesc, boolean, java.rmi.activation.ActivationSystem)
* ActivateWrapper.register()} with the provided information.
* <LI> obtains an <A HREF="#innerProxy">inner proxy</A> by calling
* {@link java.rmi.activation.ActivationID#activate(boolean)
* activate(true)} on the object returned from
* <code>ActivateWrapper.register()</code>, which also
* activates the service instance.
* <LI> obtains the service proxy in the following
* order of precedence:
* <UL>
* <LI>if the <A HREF="#innerProxy">inner proxy</A> implements
* {@link ServiceProxyAccessor} then the return value of
* {@link ServiceProxyAccessor#getServiceProxy() getServiceProxy}
* is used
* <LI>if the <A HREF="#innerProxy">inner proxy</A> is
* not <code>null</code> then it is used
* <LI>Otherwise, <code>null</code> is used
* </UL>
*
* @return a
* {@link com.sun.jini.start.SharedActivatableServiceDescriptor.Created
* Created} object that contains the group identifier, activation ID, and
* proxy associated with the newly created service instance.
* @throws java.lang.Exception Thrown if there was any problem
* creating the object.
*
*/
public Object create(Configuration config) throws Exception {
ServiceStarter.ensureSecurityManager();
logger.entering(SharedActivatableServiceDescriptor.class.getName(),
"create", new Object[] {config});
if (config == null) {
throw new NullPointerException(
"Configuration argument cannot be null");
}
ProxyPreparer globalServicePreparer =
(ProxyPreparer) Config.getNonNullEntry(config,
ServiceStarter.START_PACKAGE,
"servicePreparer", ProxyPreparer.class,
new BasicProxyPreparer());
// Needs to be called prior to setting the "descCreated" flag
if (getServicePreparer() == null) {
setServicePreparer(globalServicePreparer);
}
synchronized (descCreatedLock) {
descCreated = true;
}
// Get prepared activation system reference
Created created = null;
ActivationSystem sys =
ServiceStarter.getActivationSystem(
getActivationSystemHost(),
getActivationSystemPort(),
config);
ProxyPreparer activationIDPreparer =
(ProxyPreparer) Config.getNonNullEntry(config,
ServiceStarter.START_PACKAGE, "activationIdPreparer",
ProxyPreparer.class, new BasicProxyPreparer());
if (innerProxyPreparer == null) {
innerProxyPreparer = globalServicePreparer;
}
/* Warn user of inaccessible codebase(s) */
HTTPDStatus.httpdWarning(getExportCodebase());
ActivationGroupID gid = null;
ActivationID aid = null;
Object proxy = null;
try {
/* Create the ActivateWrapper descriptor for the desired service */
MarshalledObject params =
new MarshalledObject(getServerConfigArgs());
ActivateWrapper.ActivateDesc adesc =
new ActivateWrapper.ActivateDesc(
getImplClassName(),
ClassLoaderUtil.getImportCodebaseURLs(getImportCodebase()),
ClassLoaderUtil.getCodebaseURLs(getExportCodebase()),
getPolicy(),
params);
logger.finest("ActivateDesc: " + adesc);
// Get hosting activation group
gid = SharedActivationGroupDescriptor.restoreGroupID(
getSharedGroupLog());
/* Register the desired service with the activation system */
aid = ActivateWrapper.register(
gid, adesc, getRestart(), sys);
aid = (ActivationID) activationIDPreparer.prepareProxy(aid);
proxy = aid.activate(true);
if(proxy != null) {
proxy = innerProxyPreparer.prepareProxy(proxy);
if (proxy instanceof ServiceProxyAccessor) {
proxy = ((ServiceProxyAccessor)proxy).getServiceProxy();
if(proxy != null) {
proxy = getServicePreparer().prepareProxy(proxy);
} else {
logger.log(Level.FINE,
"Service's getServiceProxy() returned null");
}
}
}//endif
} catch(Exception e) {
try {
if (aid != null) sys.unregisterObject(aid);
} catch (Exception ee) {
// ignore -- did the best we could.
logger.log(Level.FINEST,
"Unable to unregister with activation system", ee);
}
if (e instanceof IOException)
throw (IOException)e;
else if (e instanceof ActivationException)
throw (ActivationException)e;
else if (e instanceof ClassNotFoundException)
throw (ClassNotFoundException)e;
else
throw new RuntimeException("Unexpected Exception", e);
}
created = new Created(gid, aid, proxy);
logger.exiting(SharedActivatableServiceDescriptor.class.getName(),
"create", created);
return created;
}
public String toString() {
// Would like to call super(), but need different formatting
ArrayList fields = new ArrayList(12);
fields.add(getExportCodebase());
fields.add(getPolicy());
fields.add(getImportCodebase());
fields.add(getImplClassName());
fields.add(
((getServerConfigArgs() == null)
? null : Arrays.asList(getServerConfigArgs())));
fields.add(getLifeCycle());
fields.add(getServicePreparer());
fields.add(getInnerProxyPreparer());
fields.add(sharedGroupLog);
fields.add(Boolean.valueOf(restart));
fields.add(host);
fields.add(new Integer(port));
return fields.toString();
}
/**
* Reads the default serializable field values for this object.
* Also, verifies that the deserialized values are legal.
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
// Verify serialized fields
if (sharedGroupLog == null) {
throw new InvalidObjectException("null shared group log");
}
if (host == null) {
throw new InvalidObjectException("null activation host name");
}
if (port <= 0) {
throw new InvalidObjectException("invalid activation port value: " + port);
}
//Reinitialize transient fields upon de-serialization.
innerProxyPreparer = null;
}
/**
* Throws InvalidObjectException, since data for this class is required.
*/
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("no data");
}
}