|
@@ -0,0 +1,501 @@
|
|
|
+/**
|
|
|
+ * 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.zookeeper.server.admin;
|
|
|
+
|
|
|
+import java.lang.management.ManagementFactory;
|
|
|
+import java.lang.management.OperatingSystemMXBean;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+import org.apache.zookeeper.Environment;
|
|
|
+import org.apache.zookeeper.Environment.Entry;
|
|
|
+import org.apache.zookeeper.Version;
|
|
|
+import org.apache.zookeeper.server.DataTree;
|
|
|
+import org.apache.zookeeper.server.ServerStats;
|
|
|
+import org.apache.zookeeper.server.ZKDatabase;
|
|
|
+import org.apache.zookeeper.server.ZooKeeperServer;
|
|
|
+import org.apache.zookeeper.server.ZooTrace;
|
|
|
+import org.apache.zookeeper.server.quorum.Leader;
|
|
|
+import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer;
|
|
|
+import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import com.sun.management.UnixOperatingSystemMXBean;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Class containing static methods for registering and running Commands, as well
|
|
|
+ * as default Command definitions.
|
|
|
+ *
|
|
|
+ * @see Command
|
|
|
+ * @see JettyAdminServer
|
|
|
+ */
|
|
|
+public class Commands {
|
|
|
+ static final Logger LOG = LoggerFactory.getLogger(Commands.class);
|
|
|
+
|
|
|
+ /** Maps command names to Command instances */
|
|
|
+ private static Map<String, Command> commands = new HashMap<String, Command>();
|
|
|
+ private static Set<String> primaryNames = new HashSet<String>();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Registers the given command. Registered commands can be run by passing
|
|
|
+ * any of their names to runCommand.
|
|
|
+ */
|
|
|
+ public static void registerCommand(Command command) {
|
|
|
+ for (String name : command.getNames()) {
|
|
|
+ Command prev = commands.put(name, command);
|
|
|
+ if (prev != null) {
|
|
|
+ LOG.warn("Re-registering command %s (primary name = %s)", name, command.getPrimaryName());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ primaryNames.add(command.getPrimaryName());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Run the registered command with name cmdName. Commands should not produce
|
|
|
+ * any exceptions; any (anticipated) errors should be reported in the
|
|
|
+ * "error" entry of the returned map. Likewise, if no command with the given
|
|
|
+ * name is registered, this will be noted in the "error" entry.
|
|
|
+ *
|
|
|
+ * @param cmdName
|
|
|
+ * @param zkServer
|
|
|
+ * @param kwargs String-valued keyword arguments to the command
|
|
|
+ * (may be null if command requires no additional arguments)
|
|
|
+ * @return Map representing response to command containing at minimum:
|
|
|
+ * - "command" key containing the command's primary name
|
|
|
+ * - "error" key containing a String error message or null if no error
|
|
|
+ */
|
|
|
+ public static CommandResponse runCommand(String cmdName, ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ if (!commands.containsKey(cmdName)) {
|
|
|
+ return new CommandResponse(cmdName, "Unknown command: " + cmdName);
|
|
|
+ }
|
|
|
+ if (zkServer == null) {
|
|
|
+ return new CommandResponse(cmdName, "This ZooKeeper instance is not currently serving requests");
|
|
|
+ }
|
|
|
+ return commands.get(cmdName).run(zkServer, kwargs);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the primary names of all registered commands.
|
|
|
+ */
|
|
|
+ public static Set<String> getPrimaryNames() {
|
|
|
+ return primaryNames;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the commands registered under cmdName with registerCommand, or
|
|
|
+ * null if no command is registered with that name.
|
|
|
+ */
|
|
|
+ public static Command getCommand(String cmdName) {
|
|
|
+ return commands.get(cmdName);
|
|
|
+ }
|
|
|
+
|
|
|
+ static {
|
|
|
+ registerCommand(new CnxnStatResetCommand());
|
|
|
+ registerCommand(new ConfCommand());
|
|
|
+ registerCommand(new ConsCommand());
|
|
|
+ registerCommand(new DumpCommand());
|
|
|
+ registerCommand(new EnvCommand());
|
|
|
+ registerCommand(new GetTraceMaskCommand());
|
|
|
+ registerCommand(new IsroCommand());
|
|
|
+ registerCommand(new MonitorCommand());
|
|
|
+ registerCommand(new RuokCommand());
|
|
|
+ registerCommand(new SetTraceMaskCommand());
|
|
|
+ registerCommand(new SrvrCommand());
|
|
|
+ registerCommand(new StatCommand());
|
|
|
+ registerCommand(new StatResetCommand());
|
|
|
+ registerCommand(new WatchCommand());
|
|
|
+ registerCommand(new WatchesByPathCommand());
|
|
|
+ registerCommand(new WatchSummaryCommand());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Reset all connection statistics.
|
|
|
+ */
|
|
|
+ public static class CnxnStatResetCommand extends CommandBase {
|
|
|
+ public CnxnStatResetCommand() {
|
|
|
+ super(Arrays.asList("connection_stat_reset", "crst"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ zkServer.getServerCnxnFactory().resetAllConnectionStats();
|
|
|
+ return response;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Server configuration parameters.
|
|
|
+ * @see ZooKeeperServer#getConf()
|
|
|
+ */
|
|
|
+ public static class ConfCommand extends CommandBase {
|
|
|
+ public ConfCommand() {
|
|
|
+ super(Arrays.asList("configuration", "conf", "config"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.putAll(zkServer.getConf().toMap());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Information on client connections to server. Returned Map contains:
|
|
|
+ * - "connections": list of connection info objects
|
|
|
+ * @see org.apache.zookeeper.server.ServerCnxn#getConnectionInfo(boolean)
|
|
|
+ */
|
|
|
+ public static class ConsCommand extends CommandBase {
|
|
|
+ public ConsCommand() {
|
|
|
+ super(Arrays.asList("connections", "cons"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("connections", zkServer.getServerCnxnFactory().getAllConnectionInfo(false));
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Information on session expirations and ephemerals. Returned map contains:
|
|
|
+ * - "expiry_time_to_session_ids": Map<Long, Set<Long>>
|
|
|
+ * time -> sessions IDs of sessions that expire at time
|
|
|
+ * - "sesssion_id_to_ephemeral_paths": Map<Long, Set<String>>
|
|
|
+ * session ID -> ephemeral paths created by that session
|
|
|
+ * @see ZooKeeperServer#getSessionExpiryMap()
|
|
|
+ * @see ZooKeeperServer#getEphemerals()
|
|
|
+ */
|
|
|
+ public static class DumpCommand extends CommandBase {
|
|
|
+ public DumpCommand() {
|
|
|
+ super(Arrays.asList("dump"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("expiry_time_to_session_ids", zkServer.getSessionExpiryMap());
|
|
|
+ response.put("session_id_to_ephemeral_paths", zkServer.getEphemerals());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * All defined environment variables.
|
|
|
+ */
|
|
|
+ public static class EnvCommand extends CommandBase {
|
|
|
+ public EnvCommand() {
|
|
|
+ super(Arrays.asList("environment", "env", "envi"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ for (Entry e : Environment.list()) {
|
|
|
+ response.put(e.getKey(), e.getValue());
|
|
|
+ }
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The current trace mask. Returned map contains:
|
|
|
+ * - "tracemask": Long
|
|
|
+ */
|
|
|
+ public static class GetTraceMaskCommand extends CommandBase {
|
|
|
+ public GetTraceMaskCommand() {
|
|
|
+ super(Arrays.asList("get_trace_mask", "gtmk"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("tracemask", ZooTrace.getTextTraceLevel());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is this server in read-only mode. Returned map contains:
|
|
|
+ * - "is_read_only": Boolean
|
|
|
+ */
|
|
|
+ public static class IsroCommand extends CommandBase {
|
|
|
+ public IsroCommand() {
|
|
|
+ super(Arrays.asList("is_read_only", "isro"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("read_only", zkServer instanceof ReadOnlyZooKeeperServer);
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Some useful info for monitoring. Returned map contains:
|
|
|
+ * - "version": String
|
|
|
+ * server version
|
|
|
+ * - "avg_latency": Long
|
|
|
+ * - "max_latency": Long
|
|
|
+ * - "min_latency": Long
|
|
|
+ * - "packets_received": Long
|
|
|
+ * - "packets_sents": Long
|
|
|
+ * - "num_alive_connections": Integer
|
|
|
+ * - "outstanding_requests": Long
|
|
|
+ * number of unprocessed requests
|
|
|
+ * - "server_state": "leader", "follower", or "standalone"
|
|
|
+ * - "znode_count": Integer
|
|
|
+ * - "watch_count": Integer
|
|
|
+ * - "ephemerals_count": Integer
|
|
|
+ * - "approximate_data_size": Long
|
|
|
+ * - "open_file_descriptor_count": Long (unix only)
|
|
|
+ * - "max_file_descritpor_count": Long (unix only)
|
|
|
+ * - "followers": Integer (leader only)
|
|
|
+ * - "synced_followers": Integer (leader only)
|
|
|
+ * - "pending_syncs": Integer (leader only)
|
|
|
+ */
|
|
|
+ public static class MonitorCommand extends CommandBase {
|
|
|
+ public MonitorCommand() {
|
|
|
+ super(Arrays.asList("monitor", "mntr"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ ZKDatabase zkdb = zkServer.getZKDatabase();
|
|
|
+ ServerStats stats = zkServer.serverStats();
|
|
|
+
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+
|
|
|
+ response.put("version", Version.getFullVersion());
|
|
|
+
|
|
|
+ response.put("avg_latency", stats.getAvgLatency());
|
|
|
+ response.put("max_latency", stats.getMaxLatency());
|
|
|
+ response.put("min_latency", stats.getMinLatency());
|
|
|
+
|
|
|
+ response.put("packets_received", stats.getPacketsReceived());
|
|
|
+ response.put("packets_sent", stats.getPacketsSent());
|
|
|
+ response.put("num_alive_connections", stats.getNumAliveClientConnections());
|
|
|
+
|
|
|
+ response.put("outstanding_requests", stats.getOutstandingRequests());
|
|
|
+
|
|
|
+ response.put("server_state", stats.getServerState());
|
|
|
+ response.put("znode_count", zkdb.getNodeCount());
|
|
|
+
|
|
|
+ response.put("watch_count", zkdb.getDataTree().getWatchCount());
|
|
|
+ response.put("ephemerals_count", zkdb.getDataTree().getEphemeralsCount());
|
|
|
+ response.put("approximate_data_size", zkdb.getDataTree().approximateDataSize());
|
|
|
+
|
|
|
+ OperatingSystemMXBean osMbean = ManagementFactory.getOperatingSystemMXBean();
|
|
|
+ if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) {
|
|
|
+ UnixOperatingSystemMXBean unixos = (UnixOperatingSystemMXBean) osMbean;
|
|
|
+
|
|
|
+ response.put("open_file_descriptor_count", unixos.getOpenFileDescriptorCount());
|
|
|
+ response.put("max_file_descriptor_count", unixos.getMaxFileDescriptorCount());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (zkServer instanceof LeaderZooKeeperServer) {
|
|
|
+ Leader leader = ((LeaderZooKeeperServer) zkServer).getLeader();
|
|
|
+
|
|
|
+ response.put("followers", leader.getLearners().size());
|
|
|
+ response.put("synced_followers", leader.getForwardingFollowers().size());
|
|
|
+ response.put("pending_syncs", leader.getNumPendingSyncs());
|
|
|
+ }
|
|
|
+
|
|
|
+ return response;
|
|
|
+
|
|
|
+ }}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * No-op command, check if the server is running
|
|
|
+ */
|
|
|
+ public static class RuokCommand extends CommandBase {
|
|
|
+ public RuokCommand() {
|
|
|
+ super(Arrays.asList("ruok"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ return initializeResponse();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets the trace mask. Required arguments:
|
|
|
+ * - "traceMask": Long
|
|
|
+ * Returned Map contains:
|
|
|
+ * - "tracemask": Long
|
|
|
+ */
|
|
|
+ public static class SetTraceMaskCommand extends CommandBase {
|
|
|
+ public SetTraceMaskCommand() {
|
|
|
+ super(Arrays.asList("set_trace_mask", "stmk"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ long traceMask;
|
|
|
+ if (!kwargs.containsKey("traceMask")) {
|
|
|
+ response.put("error", "setTraceMask requires long traceMask argument");
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ traceMask = Long.parseLong(kwargs.get("traceMask"));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ response.put("error", "setTraceMask requires long traceMask argument, got "
|
|
|
+ + kwargs.get("traceMask"));
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+
|
|
|
+ ZooTrace.setTextTraceLevel(traceMask);
|
|
|
+ response.put("tracemask", traceMask);
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Server information. Returned map contains:
|
|
|
+ * - "version": String
|
|
|
+ * version of server
|
|
|
+ * - "read_only": Boolean
|
|
|
+ * is server in read-only mode
|
|
|
+ * - "server_stats": ServerStats object
|
|
|
+ * - "node_count": Integer
|
|
|
+ */
|
|
|
+ public static class SrvrCommand extends CommandBase {
|
|
|
+ public SrvrCommand() {
|
|
|
+ super(Arrays.asList("server_stats", "srvr"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Allow subclasses (e.g. StatCommand) to specify their own names
|
|
|
+ protected SrvrCommand(List<String> names) {
|
|
|
+ super(names);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ LOG.info("running stat");
|
|
|
+ response.put("version", Version.getFullVersion());
|
|
|
+ response.put("read_only", zkServer instanceof ReadOnlyZooKeeperServer);
|
|
|
+ response.put("server_stats", zkServer.serverStats());
|
|
|
+ response.put("node_count", zkServer.getZKDatabase().getNodeCount());
|
|
|
+ return response;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Same as SrvrCommand but has extra "connections" entry.
|
|
|
+ */
|
|
|
+ public static class StatCommand extends SrvrCommand {
|
|
|
+ public StatCommand() {
|
|
|
+ super(Arrays.asList("stats", "stat"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = super.run(zkServer, kwargs);
|
|
|
+ response.put("connections", zkServer.getServerCnxnFactory().getAllConnectionInfo(true));
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Resets server statistics.
|
|
|
+ */
|
|
|
+ public static class StatResetCommand extends CommandBase {
|
|
|
+ public StatResetCommand() {
|
|
|
+ super(Arrays.asList("stat_reset", "srst"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ zkServer.serverStats().reset();
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Watch information aggregated by session. Returned Map contains:
|
|
|
+ * - "session_id_to_watched_paths": Map<Long, Set<String>> session ID -> watched paths
|
|
|
+ * @see DataTree#getWatches()
|
|
|
+ */
|
|
|
+ public static class WatchCommand extends CommandBase {
|
|
|
+ public WatchCommand() {
|
|
|
+ super(Arrays.asList("watches", "wchc"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ DataTree dt = zkServer.getZKDatabase().getDataTree();
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("session_id_to_watched_paths", dt.getWatches().toMap());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Watch information aggregated by path. Returned Map contains:
|
|
|
+ * - "path_to_session_ids": Map<String, Set<Long>> path -> session IDs of sessions watching path
|
|
|
+ * @see DataTree#getWatchesByPath()
|
|
|
+ */
|
|
|
+ public static class WatchesByPathCommand extends CommandBase {
|
|
|
+ public WatchesByPathCommand() {
|
|
|
+ super(Arrays.asList("watches_by_path", "wchp"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ DataTree dt = zkServer.getZKDatabase().getDataTree();
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.put("path_to_session_ids", dt.getWatchesByPath().toMap());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Summarized watch information.
|
|
|
+ * @see DataTree#getWatchesSummary()
|
|
|
+ */
|
|
|
+ public static class WatchSummaryCommand extends CommandBase {
|
|
|
+ public WatchSummaryCommand() {
|
|
|
+ super(Arrays.asList("watch_summary", "wchs"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
|
|
|
+ DataTree dt = zkServer.getZKDatabase().getDataTree();
|
|
|
+ CommandResponse response = initializeResponse();
|
|
|
+ response.putAll(dt.getWatchesSummary().toMap());
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Commands() {}
|
|
|
+}
|