Author: rgodfrey
Date: Wed Jun 10 23:10:20 2015
New Revision: 1684794
URL: http://svn.apache.org/r1684794
Log:
QPID-6581 : [Java Broker] Bind the rest serlvet to paths based purely on the
model meta-data
Modified:
qpid/java/trunk/bdbstore/src/main/java/resources/js/qpid/management/virtualhostnode/bdb_ha/show.js
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/BDBHAVirtualHostNodeRestTest.java
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/GroupCreator.java
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Model.java
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
qpid/java/trunk/doc/book/src/java-broker/management/channels/Java-Broker-Management-Channel-REST-API.xml
Modified:
qpid/java/trunk/bdbstore/src/main/java/resources/js/qpid/management/virtualhostnode/bdb_ha/show.js
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/bdbstore/src/main/java/resources/js/qpid/management/virtualhostnode/bdb_ha/show.js?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/bdbstore/src/main/java/resources/js/qpid/management/virtualhostnode/bdb_ha/show.js
(original)
+++
qpid/java/trunk/bdbstore/src/main/java/resources/js/qpid/management/virtualhostnode/bdb_ha/show.js
Wed Jun 10 23:10:20 2015
@@ -45,7 +45,7 @@ define(["dojo/_base/connect",
}
else
{
- return {name: remoteNodeName, type: "replicationnode", parent :
modelObj};
+ return {name: remoteNodeName, type: "remotereplicationnode", parent
: modelObj};
}
}
Modified:
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/BDBHAVirtualHostNodeRestTest.java
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/BDBHAVirtualHostNodeRestTest.java?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/BDBHAVirtualHostNodeRestTest.java
(original)
+++
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/BDBHAVirtualHostNodeRestTest.java
Wed Jun 10 23:10:20 2015
@@ -113,8 +113,8 @@ public class BDBHAVirtualHostNodeRestTes
assertActualAndDesiredStates(node3Url, "ACTIVE", "ACTIVE");
// verify that remote nodes for node1 are created and their state is
set to ACTIVE
- waitForAttributeChanged("replicationnode/" + NODE2 + "/" + NODE1,
BDBHARemoteReplicationNode.STATE, "ACTIVE");
- waitForAttributeChanged("replicationnode/" + NODE3 + "/" + NODE1,
BDBHARemoteReplicationNode.STATE, "ACTIVE");
+ waitForAttributeChanged("remotereplicationnode/" + NODE2 + "/" +
NODE1, BDBHARemoteReplicationNode.STATE, "ACTIVE");
+ waitForAttributeChanged("remotereplicationnode/" + NODE3 + "/" +
NODE1, BDBHARemoteReplicationNode.STATE, "ACTIVE");
mutateDesiredState(node1Url, "STOPPED");
@@ -123,10 +123,10 @@ public class BDBHAVirtualHostNodeRestTes
assertActualAndDesiredStates(node3Url, "ACTIVE", "ACTIVE");
// verify that remote node state fro node1 is changed to UNAVAILABLE
- waitForAttributeChanged("replicationnode/" + NODE2 + "/" + NODE1,
BDBHARemoteReplicationNode.STATE, "UNAVAILABLE");
- waitForAttributeChanged("replicationnode/" + NODE3 + "/" + NODE1,
BDBHARemoteReplicationNode.STATE, "UNAVAILABLE");
+ waitForAttributeChanged("remotereplicationnode/" + NODE2 + "/" +
NODE1, BDBHARemoteReplicationNode.STATE, "UNAVAILABLE");
+ waitForAttributeChanged("remotereplicationnode/" + NODE3 + "/" +
NODE1, BDBHARemoteReplicationNode.STATE, "UNAVAILABLE");
- List<Map<String, Object>> remoteNodes =
getRestTestHelper().getJsonAsList("replicationnode/" + NODE2);
+ List<Map<String, Object>> remoteNodes =
getRestTestHelper().getJsonAsList("remotereplicationnode/" + NODE2);
assertEquals("Unexpected number of remote nodes on " + NODE2, 2,
remoteNodes.size());
Map<String, Object> remoteNode1 = findRemoteNodeByName(remoteNodes,
NODE1);
@@ -177,7 +177,7 @@ public class BDBHAVirtualHostNodeRestTes
assertRemoteNodes(NODE1, NODE2, NODE3);
- List<Map<String,Object>> data =
getRestTestHelper().getJsonAsList("replicationnode/" + NODE1);
+ List<Map<String,Object>> data =
getRestTestHelper().getJsonAsList("remotereplicationnode/" + NODE1);
assertEquals("Unexpected number of remote nodes on " + NODE1, 2,
data.size());
getRestTestHelper().submitRequest(_baseNodeRestUrl + NODE2, "DELETE",
HttpServletResponse.SC_OK);
@@ -185,7 +185,7 @@ public class BDBHAVirtualHostNodeRestTes
int counter = 0;
while (data.size() != 1 && counter<50)
{
- data = getRestTestHelper().getJsonAsList("replicationnode/" +
NODE1);
+ data = getRestTestHelper().getJsonAsList("remotereplicationnode/"
+ NODE1);
if (data.size() != 1)
{
Thread.sleep(100l);
@@ -210,7 +210,7 @@ public class BDBHAVirtualHostNodeRestTes
Collections.<String,
Object>singletonMap(BDBHAVirtualHostNode.PRIORITY, 100),
HttpServletResponse.SC_OK);
- List<Map<String,Object>> data =
getRestTestHelper().getJsonAsList("replicationnode/" + NODE2);
+ List<Map<String,Object>> data =
getRestTestHelper().getJsonAsList("remotereplicationnode/" + NODE2);
assertEquals("Unexpected number of remote nodes on " + NODE2, 2,
data.size());
// delete master
@@ -220,12 +220,12 @@ public class BDBHAVirtualHostNodeRestTes
waitForAttributeChanged(_baseNodeRestUrl + NODE2 + "?depth=0",
BDBHAVirtualHostNode.ROLE, "MASTER");
// delete remote node
- getRestTestHelper().submitRequest("replicationnode/" + NODE2 + "/" +
NODE1, "DELETE", HttpServletResponse.SC_OK);
+ getRestTestHelper().submitRequest("remotereplicationnode/" + NODE2 +
"/" + NODE1, "DELETE", HttpServletResponse.SC_OK);
int counter = 0;
while (data.size() != 1 && counter<50)
{
- data = getRestTestHelper().getJsonAsList("replicationnode/" +
NODE2);
+ data = getRestTestHelper().getJsonAsList("remotereplicationnode/"
+ NODE2);
if (data.size() != 1)
{
Thread.sleep(100l);
@@ -391,7 +391,7 @@ public class BDBHAVirtualHostNodeRestTes
remotes.remove(clusterNodeName);
for (String remote : remotes)
{
- String remoteUrl = "replicationnode/" + clusterNodeName + "/"
+ remote;
+ String remoteUrl = "remotereplicationnode/" + clusterNodeName
+ "/" + remote;
Map<String, Object> nodeData =
waitForAttributeChanged(remoteUrl, BDBHARemoteReplicationNode.ROLE,
remote.equals(masterNode) ? "MASTER" : "REPLICA");
assertRemoteNodeData(remote, nodeData);
}
Modified:
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/GroupCreator.java
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/GroupCreator.java?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/GroupCreator.java
(original)
+++
qpid/java/trunk/bdbstore/systests/src/test/java/org/apache/qpid/server/store/berkeleydb/replication/GroupCreator.java
Wed Jun 10 23:10:20 2015
@@ -438,7 +438,7 @@ public class GroupCreator
}
else
{
- url = "/api/latest/replicationnode/" + localNodeName + "/" +
remoteNodeName;
+ url = "/api/latest/remotereplicationnode/" + localNodeName + "/" +
remoteNodeName;
}
return url;
}
Modified:
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Model.java
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Model.java?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Model.java
(original)
+++
qpid/java/trunk/broker-core/src/main/java/org/apache/qpid/server/model/Model.java
Wed Jun 10 23:10:20 2015
@@ -145,6 +145,33 @@ public abstract class Model
return allDescendants.contains(descendantClass);
}
+ public final Collection<Class<? extends ConfiguredObject>>
getDescendantCategories(Class<? extends ConfiguredObject> parent)
+ {
+ Set<Class<? extends ConfiguredObject>> allDescendants = new
HashSet<>();
+ for(Class<? extends ConfiguredObject> clazz : getChildTypes(parent))
+ {
+ if(allDescendants.add(clazz))
+ {
+ allDescendants.addAll(getDescendantCategories(clazz));
+ }
+ }
+
+ return allDescendants;
+ }
+
+ public final Collection<Class<? extends ConfiguredObject>>
getAncestorCategories(Class<? extends ConfiguredObject> category)
+ {
+ Set<Class<? extends ConfiguredObject>> allAncestors = new HashSet<>();
+ for(Class<? extends ConfiguredObject> clazz : getParentTypes(category))
+ {
+ if(allAncestors.add(clazz))
+ {
+ allAncestors.addAll(getAncestorCategories(clazz));
+ }
+ }
+
+ return allAncestors;
+ }
public abstract Collection<Class<? extends ConfiguredObject>>
getSupportedCategories();
public abstract Collection<Class<? extends ConfiguredObject>>
getChildTypes(Class<? extends ConfiguredObject> parent);
Modified:
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
(original)
+++
qpid/java/trunk/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
Wed Jun 10 23:10:20 2015
@@ -43,7 +43,6 @@ import javax.servlet.http.HttpServletReq
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
-import org.apache.qpid.server.management.plugin.servlet.rest.TimeZoneServlet;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Request;
@@ -68,9 +67,7 @@ import org.apache.qpid.server.management
import
org.apache.qpid.server.management.plugin.filter.RedirectingAuthorisationFilter;
import org.apache.qpid.server.management.plugin.servlet.DefinedFileServlet;
import org.apache.qpid.server.management.plugin.servlet.FileServlet;
-import org.apache.qpid.server.management.plugin.servlet.LogFileServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.ApiDocsServlet;
-import
org.apache.qpid.server.management.plugin.servlet.rest.LogFileListingServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.LogRecordsServlet;
import
org.apache.qpid.server.management.plugin.servlet.rest.LoggedOnUserPreferencesServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.LogoutServlet;
@@ -81,6 +78,7 @@ import org.apache.qpid.server.management
import org.apache.qpid.server.management.plugin.servlet.rest.RestServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.SaslServlet;
import org.apache.qpid.server.management.plugin.servlet.rest.StructureServlet;
+import org.apache.qpid.server.management.plugin.servlet.rest.TimeZoneServlet;
import
org.apache.qpid.server.management.plugin.servlet.rest.UserPreferencesServlet;
import org.apache.qpid.server.model.*;
import org.apache.qpid.server.model.adapter.AbstractPluginAdapter;
@@ -95,7 +93,7 @@ public class HttpManagement extends Abst
{
private static final String PORT_SERVLET_ATTRIBUTE =
"org.apache.qpid.server.model.Port";
private final Logger _logger =
LoggerFactory.getLogger(HttpManagement.class);
-
+
// 10 minutes by default
public static final int DEFAULT_TIMEOUT_IN_SECONDS = 60 * 10;
public static final String TIME_OUT = "sessionTimeout";
@@ -110,6 +108,9 @@ public class HttpManagement extends Abst
private static final String JSESSIONID_COOKIE_PREFIX = "JSESSIONID_";
+ private static final String[] STATIC_FILE_TYPES = { "*.js", "*.css",
"*.html", "*.png", "*.gif", "*.jpg",
+ "*.jpeg", "*.json",
"*.txt", "*.xsl" };
+
private Server _server;
@ManagedAttributeField
@@ -300,32 +301,8 @@ public class HttpManagement extends Abst
root.addFilter(new FilterHolder(new RedirectingAuthorisationFilter()),
"/index.html", EnumSet.of(DispatcherType.REQUEST));
root.addFilter(new FilterHolder(new RedirectingAuthorisationFilter()),
"/", EnumSet.of(DispatcherType.REQUEST));
- addRestServlet(root, "broker");
- addRestServlet(root, "virtualhostnode", VirtualHostNode.class);
- addRestServlet(root, "authenticationprovider",
AuthenticationProvider.class);
- addRestServlet(root, "accesscontrolprovider",
AccessControlProvider.class);
- addRestServlet(root, "user", AuthenticationProvider.class, User.class);
- addRestServlet(root, "groupprovider", GroupProvider.class);
- addRestServlet(root, "group", GroupProvider.class, Group.class);
- addRestServlet(root, "groupmember", GroupProvider.class, Group.class,
GroupMember.class);
- addRestServlet(root, "port", Port.class);
- addRestServlet(root, "keystore", KeyStore.class);
- addRestServlet(root, "truststore", TrustStore.class);
- addRestServlet(root, "plugin", Plugin.class);
- addRestServlet(root, "preferencesprovider",
AuthenticationProvider.class, PreferencesProvider.class);
-
- addRestServlet(root, "replicationnode", VirtualHostNode.class,
RemoteReplicationNode.class);
-
- addRestServlet(root, "virtualhost", VirtualHostNode.class,
VirtualHost.class);
- addRestServlet(root, "exchange", VirtualHostNode.class,
VirtualHost.class, Exchange.class);
- addRestServlet(root, "queue", VirtualHostNode.class,
VirtualHost.class, Queue.class);
- addRestServlet(root, "connection", VirtualHostNode.class,
VirtualHost.class, Connection.class);
- addRestServlet(root, "binding", VirtualHostNode.class,
VirtualHost.class, Exchange.class, Queue.class, Binding.class);
- addRestServlet(root, "session", VirtualHostNode.class,
VirtualHost.class, Connection.class, Session.class);
-
- addRestServlet(root, "brokerlogger", BrokerLogger.class);
- addRestServlet(root, "brokerloggerfilter", BrokerLogger.class,
BrokerLoggerFilter.class);
-
+ addRestServlet(root, Broker.class);
+
ServletHolder apiDocsServlet = new ServletHolder(new
ApiDocsServlet(getModel(), Collections.<String>emptyList()));
root.addServlet(apiDocsServlet, "/apidocs");
root.addServlet(apiDocsServlet, "/apidocs/");
@@ -353,16 +330,11 @@ public class HttpManagement extends Abst
root.addServlet(new ServletHolder(new
FileServlet(DojoHelper.getDijitPath(), true)), "/dojo/dijit/*");
root.addServlet(new ServletHolder(new
FileServlet(DojoHelper.getDojoxPath(), true)), "/dojo/dojox/*");
- root.addServlet(new ServletHolder(new FileServlet()), "*.js");
- root.addServlet(new ServletHolder(new FileServlet()), "*.css");
- root.addServlet(new ServletHolder(new FileServlet()), "*.html");
- root.addServlet(new ServletHolder(new FileServlet()), "*.png");
- root.addServlet(new ServletHolder(new FileServlet()), "*.gif");
- root.addServlet(new ServletHolder(new FileServlet()), "*.jpg");
- root.addServlet(new ServletHolder(new FileServlet()), "*.jpeg");
- root.addServlet(new ServletHolder(new FileServlet()), "*.json");
- root.addServlet(new ServletHolder(new FileServlet()), "*.txt");
- root.addServlet(new ServletHolder(new FileServlet()), "*.xsl");
+ for(String pattern : STATIC_FILE_TYPES)
+ {
+ root.addServlet(new ServletHolder(new FileServlet()), pattern);
+ }
+
root.addServlet(new ServletHolder(new TimeZoneServlet()),
"/service/timezones");
// QPID-6516
// root.addServlet(new ServletHolder(new LogFileListingServlet()),
"/service/logfilenames");
@@ -486,31 +458,58 @@ public class HttpManagement extends Abst
return connector;
}
- private void addRestServlet(ServletContextHandler root, String name,
Class<? extends ConfiguredObject>... hierarchy)
+ private void addRestServlet(ServletContextHandler root, final Class<?
extends ConfiguredObject> rootClass)
{
- ServletHolder servletHolder = new ServletHolder(name, new
RestServlet(hierarchy));
- servletHolder.getRegistration().setMultipartConfig(
- new MultipartConfigElement("",
- getContextValue(Long.class,
MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME),
- -1l,
- getContextValue(Integer.class,
MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)));
-
- List<String> paths = Arrays.asList("/api/latest/" + name ,
- "/api/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name );
-
- for(String path : paths)
- {
- root.addServlet(servletHolder, path + "/*");
- }
- ServletHolder docServletHolder = new ServletHolder(name+"docs", new
ApiDocsServlet(getModel(),
-
paths,
-
hierarchy));
- root.addServlet(docServletHolder, "/apidocs/latest/" + name + "/");
- root.addServlet(docServletHolder, "/apidocs/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name +"/");
- root.addServlet(docServletHolder, "/apidocs/latest/" + name );
- root.addServlet(docServletHolder, "/apidocs/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name);
+ Set<Class<? extends ConfiguredObject>> categories = new
HashSet<>(getModel().getDescendantCategories(rootClass));
+ categories.add(rootClass);
+ for(Class<? extends ConfiguredObject> category : categories)
+ {
+ String name = category.getSimpleName().toLowerCase();
+ List<Class<? extends ConfiguredObject>> hierarchyList = new
ArrayList<>();
+ if(category != rootClass)
+ {
+ Collection<Class<? extends ConfiguredObject>>
parentCategories;
+
+ hierarchyList.add(category);
+ while (!(parentCategories =
getModel().getParentTypes(category)).contains(rootClass))
+ {
+ hierarchyList.addAll(parentCategories);
+ category = parentCategories.iterator().next();
+ }
+
+ Collections.reverse(hierarchyList);
+
+ }
+ Class<? extends ConfiguredObject>[] hierarchyArray =
+ hierarchyList.toArray(new Class[hierarchyList.size()]);
+
+ ServletHolder servletHolder = new ServletHolder(name, new
RestServlet(hierarchyArray));
+ servletHolder.getRegistration().setMultipartConfig(
+ new MultipartConfigElement("",
+ getContextValue(Long.class,
MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME),
+ -1l,
+
getContextValue(Integer.class,
+
MAX_HTTP_FILE_UPLOAD_SIZE_CONTEXT_NAME)));
+
+ List<String> paths = Arrays.asList("/api/latest/" + name,
+ "/api/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name);
+
+ for (String path : paths)
+ {
+ root.addServlet(servletHolder, path + "/*");
+ }
+ ServletHolder docServletHolder = new ServletHolder(name +
"docs", new ApiDocsServlet(getModel(),
+
paths,
+
hierarchyArray));
+ root.addServlet(docServletHolder, "/apidocs/latest/" + name +
"/");
+ root.addServlet(docServletHolder, "/apidocs/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name + "/");
+ root.addServlet(docServletHolder, "/apidocs/latest/" + name);
+ root.addServlet(docServletHolder, "/apidocs/v" +
BrokerModel.MODEL_MAJOR_VERSION + "/" + name);
+
+
+ }
}
private void logOperationalListenMessages(Collection<Port<?>> ports)
Modified:
qpid/java/trunk/doc/book/src/java-broker/management/channels/Java-Broker-Management-Channel-REST-API.xml
URL:
http://svn.apache.org/viewvc/qpid/java/trunk/doc/book/src/java-broker/management/channels/Java-Broker-Management-Channel-REST-API.xml?rev=1684794&r1=1684793&r2=1684794&view=diff
==============================================================================
---
qpid/java/trunk/doc/book/src/java-broker/management/channels/Java-Broker-Management-Channel-REST-API.xml
(original)
+++
qpid/java/trunk/doc/book/src/java-broker/management/channels/Java-Broker-Management-Channel-REST-API.xml
Wed Jun 10 23:10:20 2015
@@ -334,16 +334,16 @@
<row>
<entry>
<itemizedlist>
- <listitem><para>/api/latest/replicationnode</para></listitem>
-
<listitem><para>/api/<version>/replicationnode</para></listitem>
- <listitem><para>/api/latest/replicationnode/<virtual host
node name></para></listitem>
-
<listitem><para>/api/<version>/replicationnode/<virtual host node
name></para></listitem>
- <listitem><para>/api/latest/replicationnode/<virtual host
node name>/<replication node name></para></listitem>
-
<listitem><para>/api/<version>/replicationnode/<virtual host node
name>/<replication node name></para></listitem>
+
<listitem><para>/api/latest/remotereplicationnode</para></listitem>
+
<listitem><para>/api/<version>/remotereplicationnode</para></listitem>
+ <listitem><para>/api/latest/remotereplicationnode/<virtual
host node name></para></listitem>
+
<listitem><para>/api/<version>/remotereplicationnode/<virtual host
node name></para></listitem>
+ <listitem><para>/api/latest/remotereplicationnode/<virtual
host node name>/<replication node name></para></listitem>
+
<listitem><para>/api/<version>/remotereplicationnode/<virtual host
node name>/<replication node name></para></listitem>
</itemizedlist>
</entry>
<entry>
- <para>Manages(creates/deletes/updates) ReplicationNode(s) on
VirtualHostNode and provides the information about current values of
ReplicationNode attributes</para>
+ <para>Manages(creates/deletes/updates) RemoteReplicationNode(s)
on VirtualHostNode and provides the information about current values of
RemoteReplicationNode attributes</para>
</entry>
</row>
<row>
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]