Modified: qpid/trunk/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java (original) +++ qpid/trunk/qpid/java/broker-plugins/management-http/src/test/java/org/apache/qpid/server/management/plugin/session/LoginLogoutReporterTest.java Fri Mar 7 16:36:26 2014 @@ -19,16 +19,20 @@ */ package org.apache.qpid.server.management.plugin.session; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.when; import javax.security.auth.Subject; -import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.SystemLog; import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; import org.mockito.ArgumentMatcher; -import org.mockito.Mockito; import junit.framework.TestCase; @@ -36,7 +40,7 @@ public class LoginLogoutReporterTest ext { private LoginLogoutReporter _loginLogoutReport; private Subject _subject = new Subject(); - private LogActor _logActor = Mockito.mock(LogActor.class); + private RootMessageLogger _logger = mock(RootMessageLogger.class); @Override protected void setUp() throws Exception @@ -44,19 +48,22 @@ public class LoginLogoutReporterTest ext super.setUp(); _subject.getPrincipals().add(new AuthenticatedPrincipal("mockusername")); - _loginLogoutReport = new LoginLogoutReporter(_logActor, _subject); + when(_logger.isEnabled()).thenReturn(true); + when(_logger.isMessageEnabled(anyString())).thenReturn(true); + SystemLog.setRootMessageLogger(_logger); + _loginLogoutReport = new LoginLogoutReporter(_subject); } public void testLoginLogged() { _loginLogoutReport.valueBound(null); - verify(_logActor).message(isLogMessageWithMessage("MNG-1007 : Open : User mockusername")); + verify(_logger).message(isLogMessageWithMessage("MNG-1007 : Open : User mockusername")); } public void testLogoutLogged() { _loginLogoutReport.valueUnbound(null); - verify(_logActor).message(isLogMessageWithMessage("MNG-1008 : Close : User mockusername")); + verify(_logger).message(isLogMessageWithMessage("MNG-1008 : Close : User mockusername")); } private LogMessage isLogMessageWithMessage(final String expectedMessage)
Modified: qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java (original) +++ qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/JMXManagedObjectRegistry.java Fri Mar 7 16:36:26 2014 @@ -20,10 +20,9 @@ */ package org.apache.qpid.server.jmx; -import javax.net.ssl.KeyManager; import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.BrokerProperties; -import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.SystemLog; import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.KeyStore; @@ -32,7 +31,6 @@ import org.apache.qpid.server.model.Tran import org.apache.qpid.server.security.auth.jmx.JMXPasswordAuthenticator; import org.apache.qpid.server.util.ServerScopedRuntimeException; -import org.apache.qpid.ssl.SSLContextFactory; import javax.management.JMException; import javax.management.MBeanServer; @@ -99,12 +97,12 @@ public class JMXManagedObjectRegistry im @Override public void start() throws IOException { - CurrentActor.get().message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); + SystemLog.message(ManagementConsoleMessages.STARTUP(OPERATIONAL_LOGGING_NAME)); //check if system properties are set to use the JVM's out-of-the-box JMXAgent if (areOutOfTheBoxJMXOptionsSet()) { - CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); + SystemLog.message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); } else { @@ -138,7 +136,7 @@ public class JMXManagedObjectRegistry im throw new ServerScopedRuntimeException("Unable to create SSLContext for key store", e); } - CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(keyStore.getName())); + SystemLog.message(ManagementConsoleMessages.SSL_KEYSTORE(keyStore.getName())); //create the SSL RMI socket factories csf = new SslRMIClientSocketFactory(); @@ -250,8 +248,8 @@ public class JMXManagedObjectRegistry im _cs.start(); String connectorServer = (connectorSslEnabled ? "SSL " : "") + "JMX RMIConnectorServer"; - CurrentActor.get().message(ManagementConsoleMessages.LISTENING(connectorServer, jmxPortConnectorServer)); - CurrentActor.get().message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); + SystemLog.message(ManagementConsoleMessages.LISTENING(connectorServer, jmxPortConnectorServer)); + SystemLog.message(ManagementConsoleMessages.READY(OPERATIONAL_LOGGING_NAME)); } private Registry createRmiRegistry(int jmxPortRegistryServer, boolean useCustomRmiRegistry) @@ -269,7 +267,7 @@ public class JMXManagedObjectRegistry im rmiRegistry = LocateRegistry.createRegistry(jmxPortRegistryServer, null, null); } - CurrentActor.get().message(ManagementConsoleMessages.LISTENING("RMI Registry", jmxPortRegistryServer)); + SystemLog.message(ManagementConsoleMessages.LISTENING("RMI Registry", jmxPortRegistryServer)); return rmiRegistry; } @@ -294,7 +292,7 @@ public class JMXManagedObjectRegistry im unregisterAllMbeans(); - CurrentActor.get().message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); + SystemLog.message(ManagementConsoleMessages.STOPPED(OPERATIONAL_LOGGING_NAME)); } private void closeConnectorAndRegistryServers() @@ -338,7 +336,7 @@ public class JMXManagedObjectRegistry im if (_rmiRegistry != null) { // Stopping the RMI registry - CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _registryPort.getPort())); + SystemLog.message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _registryPort.getPort())); try { boolean success = UnicastRemoteObject.unexportObject(_rmiRegistry, false); @@ -365,7 +363,8 @@ public class JMXManagedObjectRegistry im // Stopping the JMX ConnectorServer try { - CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", _cs.getAddress().getPort())); + SystemLog.message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", + _cs.getAddress().getPort())); _cs.stop(); } catch (IOException e) Modified: qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java (original) +++ qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/MBeanInvocationHandlerImpl.java Fri Mar 7 16:36:26 2014 @@ -23,8 +23,6 @@ package org.apache.qpid.server.jmx; import org.apache.log4j.Logger; import org.apache.qpid.server.configuration.BrokerProperties; -import org.apache.qpid.server.logging.actors.CurrentActor; -import org.apache.qpid.server.logging.actors.ManagementActor; import org.apache.qpid.server.model.Broker; import org.apache.qpid.server.model.VirtualHost; import org.apache.qpid.server.security.SecurityManager; @@ -39,17 +37,13 @@ import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.RuntimeErrorException; import javax.management.remote.MBeanServerForwarder; -import javax.management.remote.rmi.RMIServer; import javax.security.auth.Subject; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.rmi.server.RemoteServer; -import java.rmi.server.ServerNotActiveException; import java.security.AccessControlContext; import java.security.AccessController; -import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Arrays; @@ -64,7 +58,6 @@ public class MBeanInvocationHandlerImpl private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; private MBeanServer _mbs; - private final ManagementActor _logActor; private final boolean _managementRightsInferAllAccess; private final Broker _broker; @@ -73,7 +66,6 @@ public class MBeanInvocationHandlerImpl { _managementRightsInferAllAccess = Boolean.valueOf(System.getProperty(BrokerProperties.PROPERTY_MANAGEMENT_RIGHTS_INFER_ALL_ACCESS, "true")); _broker = broker; - _logActor = new ManagementActor(broker.getRootMessageLogger()); } public static MBeanServerForwarder newProxyInstance(Broker broker) @@ -117,9 +109,9 @@ public class MBeanInvocationHandlerImpl return false; } - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - String methodName = method.getName(); + final String methodName = method.getName(); if (methodName.equals("getMBeanServer")) { @@ -167,16 +159,7 @@ public class MBeanInvocationHandlerImpl throw new SecurityException("Access denied: no authenticated principal", e); } - // Save the subject - CurrentActor.set(_logActor); - try - { - return authoriseAndInvoke(method, args); - } - finally - { - CurrentActor.remove(); - } + return authoriseAndInvoke(method, args); } catch (InvocationTargetException e) { @@ -207,7 +190,7 @@ public class MBeanInvocationHandlerImpl } } - private Object authoriseAndInvoke(final Method method, final Object[] args) throws Throwable + private Object authoriseAndInvoke(final Method method, final Object[] args) throws Exception { String methodName; // Get the component, type and impact, which may be null @@ -239,8 +222,11 @@ public class MBeanInvocationHandlerImpl { try { + Subject subject = Subject.getSubject(AccessController.getContext()); + subject = new Subject(false, subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials()); + subject.getPrincipals().addAll(SecurityManager.SYSTEM.getPrincipals()); - return Subject.doAs(SecurityManager.SYSTEM, new PrivilegedExceptionAction<Object>() + return Subject.doAs(subject, new PrivilegedExceptionAction<Object>() { @Override public Object run() throws IllegalAccessException, InvocationTargetException @@ -251,7 +237,7 @@ public class MBeanInvocationHandlerImpl } catch (PrivilegedActionException e) { - throw e.getCause(); + throw (Exception) e.getCause(); } } else Modified: qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java (original) +++ qpid/trunk/qpid/java/broker-plugins/management-jmx/src/main/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporter.java Fri Mar 7 16:36:26 2014 @@ -27,12 +27,19 @@ import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.remote.JMXConnectionNotification; +import javax.security.auth.Subject; import org.apache.log4j.Logger; -import org.apache.qpid.server.logging.LogActor; import org.apache.qpid.server.logging.RootMessageLogger; -import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.SystemLog; import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.security.auth.AuthenticatedPrincipal; +import org.apache.qpid.server.security.auth.jmx.JMXConnectionPrincipal; + +import java.rmi.server.RemoteServer; +import java.rmi.server.ServerNotActiveException; +import java.security.PrivilegedAction; +import java.util.Collections; public class ManagementLogonLogoffReporter implements NotificationListener, NotificationFilter { @@ -65,19 +72,41 @@ public class ManagementLogonLogoffReport final String[] splitConnectionId = connectionId.split(" "); user = splitConnectionId[1]; } + Subject originalSubject = new Subject(false, Collections.singleton(new AuthenticatedPrincipal(user)), Collections.emptySet(), Collections.emptySet()); + Subject subject; - // use a separate instance of actor as subject is not set on connect/disconnect - // we need to pass principal name explicitly into log actor - LogActor logActor = new ManagementActor(_rootMessageLogger, user); - if (JMXConnectionNotification.OPENED.equals(type)) + try { - logActor.message(ManagementConsoleMessages.OPEN(user)); + String clientHost = RemoteServer.getClientHost(); + subject = new Subject(false, + originalSubject.getPrincipals(), + originalSubject.getPublicCredentials(), + originalSubject.getPrivateCredentials()); + subject.getPrincipals().add(new JMXConnectionPrincipal(clientHost)); + subject.setReadOnly(); } - else if (JMXConnectionNotification.CLOSED.equals(type) || - JMXConnectionNotification.FAILED.equals(type)) + catch(ServerNotActiveException e) { - logActor.message(ManagementConsoleMessages.CLOSE(user)); + subject = originalSubject; } + final String username = user; + Subject.doAs(subject, new PrivilegedAction<Object>() + { + @Override + public Object run() + { + if (JMXConnectionNotification.OPENED.equals(type)) + { + SystemLog.message(ManagementConsoleMessages.OPEN(username)); + } + else if (JMXConnectionNotification.CLOSED.equals(type) || + JMXConnectionNotification.FAILED.equals(type)) + { + SystemLog.message(ManagementConsoleMessages.CLOSE(username)); + } + return null; + } + }); } @Override Modified: qpid/trunk/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java (original) +++ qpid/trunk/qpid/java/broker-plugins/management-jmx/src/test/java/org/apache/qpid/server/jmx/ManagementLogonLogoffReporterTest.java Fri Mar 7 16:36:26 2014 @@ -23,6 +23,7 @@ import static javax.management.remote.JM import static javax.management.remote.JMXConnectionNotification.CLOSED; import static javax.management.remote.JMXConnectionNotification.FAILED; +import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; @@ -31,10 +32,14 @@ import static org.mockito.Matchers.anySt import javax.management.remote.JMXConnectionNotification; -import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; import org.apache.qpid.server.logging.RootMessageLogger; import junit.framework.TestCase; +import org.apache.qpid.server.logging.SystemLog; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.mockito.ArgumentMatcher; public class ManagementLogonLogoffReporterTest extends TestCase { @@ -52,8 +57,8 @@ public class ManagementLogonLogoffReport _usernameAccessor = mock(UsernameAccessor.class); _rootMessageLogger = mock(RootMessageLogger.class); // Enable messaging so we can valid the generated strings - when(_rootMessageLogger.isMessageEnabled(any(LogActor.class), anyString())).thenReturn(true); - + when(_rootMessageLogger.isMessageEnabled(anyString())).thenReturn(true); + SystemLog.setRootMessageLogger(_rootMessageLogger); _reporter = new ManagementLogonLogoffReporter(_rootMessageLogger, _usernameAccessor); } @@ -64,7 +69,21 @@ public class ManagementLogonLogoffReport _reporter.handleNotification(openNotification, null); - verify(_rootMessageLogger).rawMessage("[main] MNG-1007 : Open : User jmxuser", "qpid.message.managementconsole.open"); + verify(_rootMessageLogger).message(messageMatch("MNG-1007 : Open : User jmxuser", + "qpid.message.managementconsole.open")); + } + + private LogMessage messageMatch(final String message, final String hierarchy) + { + return argThat(new ArgumentMatcher<LogMessage>() + { + @Override + public boolean matches(final Object argument) + { + LogMessage actual = (LogMessage) argument; + return actual.getLogHierarchy().equals(hierarchy) && actual.toString().equals(message); + } + }); } public void testClosedNotification() @@ -74,7 +93,7 @@ public class ManagementLogonLogoffReport _reporter.handleNotification(closeNotification, null); - verify(_rootMessageLogger).rawMessage("[main] MNG-1008 : Close : User jmxuser", "qpid.message.managementconsole.close"); + verify(_rootMessageLogger).message(messageMatch("MNG-1008 : Close : User jmxuser", "qpid.message.managementconsole.close")); } public void tesNotifiedForLogOnTypeEvents() Modified: qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java (original) +++ qpid/trunk/qpid/java/common/src/main/java/org/apache/qpid/protocol/ServerProtocolEngine.java Fri Mar 7 16:36:26 2014 @@ -20,10 +20,14 @@ */ package org.apache.qpid.protocol; +import javax.security.auth.Subject; + public interface ServerProtocolEngine extends ProtocolEngine { /** * Gets the connection ID associated with this ProtocolEngine */ long getConnectionId(); + + Subject getSubject(); } Modified: qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java (original) +++ qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java Fri Mar 7 16:36:26 2014 @@ -81,7 +81,9 @@ public class ChannelLoggingTest extends String log = getLogMessage(results, 0); // MESSAGE [con:0(guest@anonymous(3273383)/test)/ch:1] CHN-1001 : Create validateMessageID("CHN-1001", log); - assertEquals("Incorrect Channel in actor:"+fromActor(log), isBroker010()? 0 : 1, getChannelID(fromActor(log))); + final String fromActor = fromActor(log); + final int channelID = getChannelID(fromActor); + assertEquals("Incorrect Channel in actor:"+fromActor(log), isBroker010()? 0 : 1, channelID); if (!isBroker010()) { Modified: qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java (original) +++ qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java Fri Mar 7 16:36:26 2014 @@ -69,7 +69,7 @@ public class VirtualHostLoggingTest exte { List<String> vhosts = Arrays.asList("test"); - assertEquals("Each vhost did not create a store.", vhosts.size(), results.size()); + assertEquals("Each vhost did not create a store.", 2*vhosts.size(), results.size()); for (int index = 0; index < results.size(); index++) { Modified: qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/InternalBrokerHolder.java URL: http://svn.apache.org/viewvc/qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/InternalBrokerHolder.java?rev=1575315&r1=1575314&r2=1575315&view=diff ============================================================================== --- qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/InternalBrokerHolder.java (original) +++ qpid/trunk/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/InternalBrokerHolder.java Fri Mar 7 16:36:26 2014 @@ -20,11 +20,15 @@ */ package org.apache.qpid.test.utils; +import java.security.PrivilegedAction; import java.util.Set; import org.apache.log4j.Logger; import org.apache.qpid.server.Broker; +import org.apache.qpid.server.security.auth.TaskPrincipal; + +import javax.security.auth.Subject; public class InternalBrokerHolder implements BrokerHolder { @@ -57,8 +61,21 @@ public class InternalBrokerHolder implem { LOGGER.info("Shutting down Broker instance"); - _broker.shutdown(); + Subject subject = org.apache.qpid.server.security.SecurityManager.SYSTEM; + subject = new Subject(false, subject.getPrincipals(), subject.getPublicCredentials(), subject.getPrivateCredentials()); + subject.getPrincipals().add(new TaskPrincipal("Shutdown")); + + Subject.doAs(subject, new PrivilegedAction<Object>() + { + @Override + public Object run() + { + _broker.shutdown(); + return null; + } + + }); waitUntilPortsAreFree(); LOGGER.info("Broker instance shutdown"); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
