599 lines
22 KiB
Java
599 lines
22 KiB
Java
/*
|
|
* 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 org.apache.catalina.manager.host;
|
|
|
|
import java.io.IOException;
|
|
import java.io.PrintWriter;
|
|
import java.io.StringWriter;
|
|
import java.net.URLEncoder;
|
|
import java.text.MessageFormat;
|
|
import java.util.Map;
|
|
import java.util.TreeMap;
|
|
|
|
import javax.servlet.ServletException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
import org.apache.catalina.Container;
|
|
import org.apache.catalina.Host;
|
|
import org.apache.catalina.util.ServerInfo;
|
|
import org.apache.tomcat.util.res.StringManager;
|
|
import org.apache.tomcat.util.security.Escape;
|
|
|
|
/**
|
|
* Servlet that enables remote management of the virtual hosts deployed
|
|
* on the server. Normally, this functionality will be protected by a security
|
|
* constraint in the web application deployment descriptor. However,
|
|
* this requirement can be relaxed during testing.
|
|
* <p>
|
|
* The difference between the <code>HostManagerServlet</code> and this
|
|
* Servlet is that this Servlet prints out a HTML interface which
|
|
* makes it easier to administrate.
|
|
* <p>
|
|
* However if you use a software that parses the output of
|
|
* <code>HostManagerServlet</code> you won't be able to upgrade
|
|
* to this Servlet since the output are not in the
|
|
* same format as from <code>HostManagerServlet</code>
|
|
*
|
|
* @author Bip Thelin
|
|
* @author Malcolm Edgar
|
|
* @author Glenn L. Nielsen
|
|
* @author Peter Rossbach
|
|
* @see org.apache.catalina.manager.ManagerServlet
|
|
*/
|
|
public final class HTMLHostManagerServlet extends HostManagerServlet {
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
// --------------------------------------------------------- Public Methods
|
|
|
|
/**
|
|
* Process a GET request for the specified resource.
|
|
*
|
|
* @param request The servlet request we are processing
|
|
* @param response The servlet response we are creating
|
|
*
|
|
* @exception IOException if an input/output error occurs
|
|
* @exception ServletException if a servlet-specified error occurs
|
|
*/
|
|
@Override
|
|
public void doGet(HttpServletRequest request,
|
|
HttpServletResponse response)
|
|
throws IOException, ServletException {
|
|
|
|
StringManager smClient = StringManager.getManager(
|
|
Constants.Package, request.getLocales());
|
|
|
|
// Identify the request parameters that we need
|
|
String command = request.getPathInfo();
|
|
|
|
// Prepare our output writer to generate the response message
|
|
response.setContentType("text/html; charset=" + Constants.CHARSET);
|
|
|
|
String message = "";
|
|
// Process the requested command
|
|
if (command == null) {
|
|
// No command == list
|
|
} else if (command.equals("/list")) {
|
|
// Nothing to do - always generate list
|
|
} else if (command.equals("/add") || command.equals("/remove") ||
|
|
command.equals("/start") || command.equals("/stop") ||
|
|
command.equals("/persist")) {
|
|
message = smClient.getString(
|
|
"hostManagerServlet.postCommand", command);
|
|
} else {
|
|
message = smClient.getString(
|
|
"hostManagerServlet.unknownCommand", command);
|
|
}
|
|
|
|
list(request, response, message, smClient);
|
|
}
|
|
|
|
|
|
/**
|
|
* Process a POST request for the specified resource.
|
|
*
|
|
* @param request The servlet request we are processing
|
|
* @param response The servlet response we are creating
|
|
*
|
|
* @exception IOException if an input/output error occurs
|
|
* @exception ServletException if a servlet-specified error occurs
|
|
*/
|
|
@Override
|
|
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
|
throws ServletException, IOException {
|
|
|
|
StringManager smClient = StringManager.getManager(
|
|
Constants.Package, request.getLocales());
|
|
|
|
// Identify the request parameters that we need
|
|
String command = request.getPathInfo();
|
|
|
|
String name = request.getParameter("name");
|
|
|
|
// Prepare our output writer to generate the response message
|
|
response.setContentType("text/html; charset=" + Constants.CHARSET);
|
|
|
|
String message = "";
|
|
|
|
// Process the requested command
|
|
if (command == null) {
|
|
// No command == list
|
|
} else if (command.equals("/add")) {
|
|
message = add(request, name, smClient);
|
|
} else if (command.equals("/remove")) {
|
|
message = remove(name, smClient);
|
|
} else if (command.equals("/start")) {
|
|
message = start(name, smClient);
|
|
} else if (command.equals("/stop")) {
|
|
message = stop(name, smClient);
|
|
} else if (command.equals("/persist")) {
|
|
message = persist(smClient);
|
|
} else {
|
|
//Try GET
|
|
doGet(request, response);
|
|
}
|
|
|
|
list(request, response, message, smClient);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a host using the specified parameters.
|
|
*
|
|
* @param request The Servlet request
|
|
* @param name Host name
|
|
* @param smClient StringManager for the client's locale
|
|
* @return output
|
|
*/
|
|
protected String add(HttpServletRequest request,String name,
|
|
StringManager smClient) {
|
|
|
|
StringWriter stringWriter = new StringWriter();
|
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
|
|
super.add(request,printWriter,name,true, smClient);
|
|
|
|
return stringWriter.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove the specified host.
|
|
*
|
|
* @param name Host name
|
|
* @param smClient StringManager for the client's locale
|
|
* @return output
|
|
*/
|
|
protected String remove(String name, StringManager smClient) {
|
|
|
|
StringWriter stringWriter = new StringWriter();
|
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
|
|
super.remove(printWriter, name, smClient);
|
|
|
|
return stringWriter.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Start the host with the specified name.
|
|
*
|
|
* @param name Host name
|
|
* @param smClient StringManager for the client's locale
|
|
* @return output
|
|
*/
|
|
protected String start(String name, StringManager smClient) {
|
|
|
|
StringWriter stringWriter = new StringWriter();
|
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
|
|
super.start(printWriter, name, smClient);
|
|
|
|
return stringWriter.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Stop the host with the specified name.
|
|
*
|
|
* @param name Host name
|
|
* @param smClient StringManager for the client's locale
|
|
* @return output
|
|
*/
|
|
protected String stop(String name, StringManager smClient) {
|
|
|
|
StringWriter stringWriter = new StringWriter();
|
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
|
|
super.stop(printWriter, name, smClient);
|
|
|
|
return stringWriter.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Persist the current configuration to server.xml.
|
|
*
|
|
* @param smClient i18n resources localized for the client
|
|
* @return output
|
|
*/
|
|
protected String persist(StringManager smClient) {
|
|
|
|
StringWriter stringWriter = new StringWriter();
|
|
PrintWriter printWriter = new PrintWriter(stringWriter);
|
|
|
|
super.persist(printWriter, smClient);
|
|
|
|
return stringWriter.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Render a HTML list of the currently active Contexts in our virtual host,
|
|
* and memory and server status information.
|
|
*
|
|
* @param request The request
|
|
* @param response The response
|
|
* @param message a message to display
|
|
* @param smClient StringManager for the client's locale
|
|
* @throws IOException An IO error occurred
|
|
*/
|
|
public void list(HttpServletRequest request,
|
|
HttpServletResponse response,
|
|
String message,
|
|
StringManager smClient) throws IOException {
|
|
|
|
if (debug >= 1) {
|
|
log(sm.getString("hostManagerServlet.list", engine.getName()));
|
|
}
|
|
|
|
PrintWriter writer = response.getWriter();
|
|
|
|
// HTML Header Section
|
|
writer.print(org.apache.catalina.manager.Constants.HTML_HEADER_SECTION);
|
|
|
|
// Body Header Section
|
|
Object[] args = new Object[2];
|
|
args[0] = request.getContextPath();
|
|
args[1] = smClient.getString("htmlHostManagerServlet.title");
|
|
writer.print(MessageFormat.format(
|
|
org.apache.catalina.manager.Constants.BODY_HEADER_SECTION, args));
|
|
|
|
// Message Section
|
|
args = new Object[3];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.messageLabel");
|
|
if (message == null || message.length() == 0) {
|
|
args[1] = "OK";
|
|
} else {
|
|
args[1] = Escape.htmlElementContent(message);
|
|
}
|
|
writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));
|
|
|
|
// Manager Section
|
|
args = new Object[9];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.manager");
|
|
args[1] = response.encodeURL(request.getContextPath() + "/html/list");
|
|
args[2] = smClient.getString("htmlHostManagerServlet.list");
|
|
args[3] = // External link
|
|
(request.getContextPath() + "/" +
|
|
smClient.getString("htmlHostManagerServlet.helpHtmlManagerFile"));
|
|
args[4] = smClient.getString("htmlHostManagerServlet.helpHtmlManager");
|
|
args[5] = // External link
|
|
(request.getContextPath() + "/" +
|
|
smClient.getString("htmlHostManagerServlet.helpManagerFile"));
|
|
args[6] = smClient.getString("htmlHostManagerServlet.helpManager");
|
|
args[7] = response.encodeURL("/manager/status");
|
|
args[8] = smClient.getString("statusServlet.title");
|
|
writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
|
|
|
|
// Hosts Header Section
|
|
args = new Object[3];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.hostName");
|
|
args[1] = smClient.getString("htmlHostManagerServlet.hostAliases");
|
|
args[2] = smClient.getString("htmlHostManagerServlet.hostTasks");
|
|
writer.print(MessageFormat.format(HOSTS_HEADER_SECTION, args));
|
|
|
|
// Hosts Row Section
|
|
// Create sorted map of host names.
|
|
Container[] children = engine.findChildren();
|
|
String hostNames[] = new String[children.length];
|
|
for (int i = 0; i < children.length; i++)
|
|
hostNames[i] = children[i].getName();
|
|
|
|
TreeMap<String,String> sortedHostNamesMap = new TreeMap<>();
|
|
|
|
for (int i = 0; i < hostNames.length; i++) {
|
|
String displayPath = hostNames[i];
|
|
sortedHostNamesMap.put(displayPath, hostNames[i]);
|
|
}
|
|
|
|
String hostsStart =
|
|
smClient.getString("htmlHostManagerServlet.hostsStart");
|
|
String hostsStop =
|
|
smClient.getString("htmlHostManagerServlet.hostsStop");
|
|
String hostsRemove =
|
|
smClient.getString("htmlHostManagerServlet.hostsRemove");
|
|
String hostThis =
|
|
smClient.getString("htmlHostManagerServlet.hostThis");
|
|
|
|
for (Map.Entry<String, String> entry : sortedHostNamesMap.entrySet()) {
|
|
String hostName = entry.getKey();
|
|
Host host = (Host) engine.findChild(hostName);
|
|
|
|
if (host != null ) {
|
|
args = new Object[2];
|
|
args[0] = // External link
|
|
Escape.htmlElementContent(hostName);
|
|
String[] aliases = host.findAliases();
|
|
StringBuilder buf = new StringBuilder();
|
|
if (aliases.length > 0) {
|
|
buf.append(aliases[0]);
|
|
for (int j = 1; j < aliases.length; j++) {
|
|
buf.append(", ").append(aliases[j]);
|
|
}
|
|
}
|
|
|
|
if (buf.length() == 0) {
|
|
buf.append(" ");
|
|
args[1] = buf.toString();
|
|
} else {
|
|
args[1] = Escape.htmlElementContent(buf.toString());
|
|
}
|
|
|
|
writer.print
|
|
(MessageFormat.format(HOSTS_ROW_DETAILS_SECTION, args));
|
|
|
|
args = new Object[5];
|
|
if (host.getState().isAvailable()) {
|
|
args[0] = response.encodeURL
|
|
(request.getContextPath() +
|
|
"/html/stop?name=" +
|
|
URLEncoder.encode(hostName, "UTF-8"));
|
|
args[1] = hostsStop;
|
|
} else {
|
|
args[0] = response.encodeURL
|
|
(request.getContextPath() +
|
|
"/html/start?name=" +
|
|
URLEncoder.encode(hostName, "UTF-8"));
|
|
args[1] = hostsStart;
|
|
}
|
|
args[2] = response.encodeURL
|
|
(request.getContextPath() +
|
|
"/html/remove?name=" +
|
|
URLEncoder.encode(hostName, "UTF-8"));
|
|
args[3] = hostsRemove;
|
|
args[4] = hostThis;
|
|
if (host == this.installedHost) {
|
|
writer.print(MessageFormat.format(
|
|
MANAGER_HOST_ROW_BUTTON_SECTION, args));
|
|
} else {
|
|
writer.print(MessageFormat.format(
|
|
HOSTS_ROW_BUTTON_SECTION, args));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add Section
|
|
args = new Object[6];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addTitle");
|
|
args[1] = smClient.getString("htmlHostManagerServlet.addHost");
|
|
args[2] = response.encodeURL(request.getContextPath() + "/html/add");
|
|
args[3] = smClient.getString("htmlHostManagerServlet.addName");
|
|
args[4] = smClient.getString("htmlHostManagerServlet.addAliases");
|
|
args[5] = smClient.getString("htmlHostManagerServlet.addAppBase");
|
|
writer.print(MessageFormat.format(ADD_SECTION_START, args));
|
|
|
|
args = new Object[3];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addAutoDeploy");
|
|
args[1] = "autoDeploy";
|
|
args[2] = "checked";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
args[0] = smClient.getString(
|
|
"htmlHostManagerServlet.addDeployOnStartup");
|
|
args[1] = "deployOnStartup";
|
|
args[2] = "checked";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addDeployXML");
|
|
args[1] = "deployXML";
|
|
args[2] = "checked";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addUnpackWARs");
|
|
args[1] = "unpackWARs";
|
|
args[2] = "checked";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addManager");
|
|
args[1] = "manager";
|
|
args[2] = "checked";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addCopyXML");
|
|
args[1] = "copyXML";
|
|
args[2] = "";
|
|
writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
|
|
|
|
args = new Object[1];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.addButton");
|
|
writer.print(MessageFormat.format(ADD_SECTION_END, args));
|
|
|
|
// Persist Configuration Section
|
|
args = new Object[4];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.persistTitle");
|
|
args[1] = response.encodeURL(request.getContextPath() + "/html/persist");
|
|
args[2] = smClient.getString("htmlHostManagerServlet.persistAllButton");
|
|
args[3] = smClient.getString("htmlHostManagerServlet.persistAll");
|
|
writer.print(MessageFormat.format(PERSIST_SECTION, args));
|
|
|
|
// Server Header Section
|
|
args = new Object[7];
|
|
args[0] = smClient.getString("htmlHostManagerServlet.serverTitle");
|
|
args[1] = smClient.getString("htmlHostManagerServlet.serverVersion");
|
|
args[2] = smClient.getString("htmlHostManagerServlet.serverJVMVersion");
|
|
args[3] = smClient.getString("htmlHostManagerServlet.serverJVMVendor");
|
|
args[4] = smClient.getString("htmlHostManagerServlet.serverOSName");
|
|
args[5] = smClient.getString("htmlHostManagerServlet.serverOSVersion");
|
|
args[6] = smClient.getString("htmlHostManagerServlet.serverOSArch");
|
|
writer.print(MessageFormat.format
|
|
(Constants.SERVER_HEADER_SECTION, args));
|
|
|
|
// Server Row Section
|
|
args = new Object[6];
|
|
args[0] = ServerInfo.getServerInfo();
|
|
args[1] = System.getProperty("java.runtime.version");
|
|
args[2] = System.getProperty("java.vm.vendor");
|
|
args[3] = System.getProperty("os.name");
|
|
args[4] = System.getProperty("os.version");
|
|
args[5] = System.getProperty("os.arch");
|
|
writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
|
|
|
|
// HTML Tail Section
|
|
writer.print(Constants.HTML_TAIL_SECTION);
|
|
|
|
// Finish up the response
|
|
writer.flush();
|
|
writer.close();
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------ Private Constants
|
|
|
|
// These HTML sections are broken in relatively small sections, because of
|
|
// limited number of substitutions MessageFormat can process
|
|
// (maximum of 10).
|
|
|
|
private static final String HOSTS_HEADER_SECTION =
|
|
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
|
|
"<tr>\n" +
|
|
" <td colspan=\"5\" class=\"title\">{0}</td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"header-left\"><small>{0}</small></td>\n" +
|
|
" <td class=\"header-center\"><small>{1}</small></td>\n" +
|
|
" <td class=\"header-center\"><small>{2}</small></td>\n" +
|
|
"</tr>\n";
|
|
|
|
private static final String HOSTS_ROW_DETAILS_SECTION =
|
|
"<tr>\n" +
|
|
" <td class=\"row-left\"><small><a href=\"http://{0}\" "
|
|
+ Constants.REL_EXTERNAL + ">{0}</a>" +
|
|
"</small></td>\n" +
|
|
" <td class=\"row-center\"><small>{1}</small></td>\n";
|
|
|
|
private static final String MANAGER_HOST_ROW_BUTTON_SECTION =
|
|
" <td class=\"row-left\">\n" +
|
|
" <small>{4}</small>\n" +
|
|
" </td>\n" +
|
|
"</tr>\n";
|
|
|
|
private static final String HOSTS_ROW_BUTTON_SECTION =
|
|
" <td class=\"row-left\" NOWRAP>\n" +
|
|
" <form class=\"inline\" method=\"POST\" action=\"{0}\">" +
|
|
" <small><input type=\"submit\" value=\"{1}\"></small>" +
|
|
" </form>\n" +
|
|
" <form class=\"inline\" method=\"POST\" action=\"{2}\">" +
|
|
" <small><input type=\"submit\" value=\"{3}\"></small>" +
|
|
" </form>\n" +
|
|
" </td>\n" +
|
|
"</tr>\n";
|
|
|
|
private static final String ADD_SECTION_START =
|
|
"</table>\n" +
|
|
"<br>\n" +
|
|
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
|
|
"<tr>\n" +
|
|
" <td colspan=\"2\" class=\"title\">{0}</td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td colspan=\"2\" class=\"header-left\"><small>{1}</small></td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td colspan=\"2\">\n" +
|
|
"<form method=\"post\" action=\"{2}\">\n" +
|
|
"<table cellspacing=\"0\" cellpadding=\"3\">\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"row-right\">\n" +
|
|
" <small>{3}</small>\n" +
|
|
" </td>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <input type=\"text\" name=\"name\" size=\"20\">\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"row-right\">\n" +
|
|
" <small>{4}</small>\n" +
|
|
" </td>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <input type=\"text\" name=\"aliases\" size=\"64\">\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"row-right\">\n" +
|
|
" <small>{5}</small>\n" +
|
|
" </td>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <input type=\"text\" name=\"appBase\" size=\"64\">\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" ;
|
|
|
|
private static final String ADD_SECTION_BOOLEAN =
|
|
"<tr>\n" +
|
|
" <td class=\"row-right\">\n" +
|
|
" <small>{0}</small>\n" +
|
|
" </td>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <input type=\"checkbox\" name=\"{1}\" {2}>\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" ;
|
|
|
|
private static final String ADD_SECTION_END =
|
|
"<tr>\n" +
|
|
" <td class=\"row-right\">\n" +
|
|
" \n" +
|
|
" </td>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <input type=\"submit\" value=\"{0}\">\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" +
|
|
"</table>\n" +
|
|
"</form>\n" +
|
|
"</td>\n" +
|
|
"</tr>\n" +
|
|
"</table>\n" +
|
|
"<br>\n" +
|
|
"\n";
|
|
|
|
private static final String PERSIST_SECTION =
|
|
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"title\">{0}</td>\n" +
|
|
"</tr>\n" +
|
|
"<tr>\n" +
|
|
" <td class=\"row-left\">\n" +
|
|
" <form class=\"inline\" method=\"POST\" action=\"{1}\">" +
|
|
" <small><input type=\"submit\" value=\"{2}\"></small>" +
|
|
" </form> {3}\n" +
|
|
" </td>\n" +
|
|
"</tr>\n" +
|
|
"</table>\n" +
|
|
"<br>\n" +
|
|
"\n";
|
|
|
|
}
|