001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.wal; 019 020import java.io.FileNotFoundException; 021import java.io.IOException; 022import java.io.UnsupportedEncodingException; 023import java.net.URLDecoder; 024import java.nio.charset.StandardCharsets; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.List; 029import java.util.concurrent.atomic.AtomicBoolean; 030import java.util.concurrent.locks.ReadWriteLock; 031import java.util.concurrent.locks.ReentrantReadWriteLock; 032import java.util.regex.Matcher; 033import java.util.regex.Pattern; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.fs.FileStatus; 036import org.apache.hadoop.fs.FileSystem; 037import org.apache.hadoop.fs.Path; 038import org.apache.hadoop.hbase.Abortable; 039import org.apache.hadoop.hbase.HConstants; 040import org.apache.hadoop.hbase.ServerName; 041import org.apache.hadoop.hbase.client.RegionInfo; 042import org.apache.hadoop.hbase.regionserver.wal.AbstractFSWAL; 043import org.apache.hadoop.hbase.regionserver.wal.WALActionsListener; 044import org.apache.hadoop.hbase.util.Addressing; 045import org.apache.hadoop.hbase.util.CancelableProgressable; 046import org.apache.hadoop.hbase.util.CommonFSUtils; 047import org.apache.hadoop.hbase.util.RecoverLeaseFSUtils; 048import org.apache.yetus.audience.InterfaceAudience; 049import org.apache.yetus.audience.InterfaceStability; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 054 055/** 056 * Base class of a WAL Provider that returns a single thread safe WAL that writes to Hadoop FS. By 057 * default, this implementation picks a directory in Hadoop FS based on a combination of 058 * <ul> 059 * <li>the HBase root directory 060 * <li>HConstants.HREGION_LOGDIR_NAME 061 * <li>the given factory's factoryId (usually identifying the regionserver by host:port) 062 * </ul> 063 * It also uses the providerId to differentiate among files. 064 */ 065@InterfaceAudience.Private 066@InterfaceStability.Evolving 067public abstract class AbstractFSWALProvider<T extends AbstractFSWAL<?>> implements WALProvider { 068 069 private static final Logger LOG = LoggerFactory.getLogger(AbstractFSWALProvider.class); 070 071 /** Separate old log into different dir by regionserver name **/ 072 public static final String SEPARATE_OLDLOGDIR = "hbase.separate.oldlogdir.by.regionserver"; 073 public static final boolean DEFAULT_SEPARATE_OLDLOGDIR = false; 074 075 public interface Initializer { 076 /** 077 * A method to initialize a WAL reader. 078 * @param startPosition the start position you want to read from, -1 means start reading from 079 * the first WAL entry. Notice that, the first entry is not started at 080 * position as we have several headers, so typically you should not pass 0 081 * here. 082 */ 083 void init(FileSystem fs, Path path, Configuration c, long startPosition) throws IOException; 084 } 085 086 protected volatile T wal; 087 protected WALFactory factory; 088 protected Configuration conf; 089 protected List<WALActionsListener> listeners = new ArrayList<>(); 090 protected String providerId; 091 protected AtomicBoolean initialized = new AtomicBoolean(false); 092 // for default wal provider, logPrefix won't change 093 protected String logPrefix; 094 protected Abortable abortable; 095 096 /** 097 * We use walCreateLock to prevent wal recreation in different threads, and also prevent getWALs 098 * missing the newly created WAL, see HBASE-21503 for more details. 099 */ 100 private final ReadWriteLock walCreateLock = new ReentrantReadWriteLock(); 101 102 /** 103 * @param factory factory that made us, identity used for FS layout. may not be null 104 * @param conf may not be null 105 * @param providerId differentiate between providers from one factory, used for FS layout. may be 106 * null 107 */ 108 @Override 109 public void init(WALFactory factory, Configuration conf, String providerId, Abortable abortable) 110 throws IOException { 111 if (!initialized.compareAndSet(false, true)) { 112 throw new IllegalStateException("WALProvider.init should only be called once."); 113 } 114 this.factory = factory; 115 this.conf = conf; 116 this.providerId = providerId; 117 // get log prefix 118 StringBuilder sb = new StringBuilder().append(factory.factoryId); 119 if (providerId != null) { 120 if (providerId.startsWith(WAL_FILE_NAME_DELIMITER)) { 121 sb.append(providerId); 122 } else { 123 sb.append(WAL_FILE_NAME_DELIMITER).append(providerId); 124 } 125 } 126 logPrefix = sb.toString(); 127 this.abortable = abortable; 128 doInit(conf); 129 } 130 131 @Override 132 public List<WAL> getWALs() { 133 if (wal != null) { 134 return Lists.newArrayList(wal); 135 } 136 walCreateLock.readLock().lock(); 137 try { 138 if (wal == null) { 139 return Collections.emptyList(); 140 } else { 141 return Lists.newArrayList(wal); 142 } 143 } finally { 144 walCreateLock.readLock().unlock(); 145 } 146 } 147 148 @Override 149 public T getWAL(RegionInfo region) throws IOException { 150 T walCopy = wal; 151 if (walCopy != null) { 152 return walCopy; 153 } 154 walCreateLock.writeLock().lock(); 155 try { 156 walCopy = wal; 157 if (walCopy != null) { 158 return walCopy; 159 } 160 walCopy = createWAL(); 161 boolean succ = false; 162 try { 163 walCopy.init(); 164 succ = true; 165 } finally { 166 if (!succ) { 167 walCopy.close(); 168 } 169 } 170 wal = walCopy; 171 return walCopy; 172 } finally { 173 walCreateLock.writeLock().unlock(); 174 } 175 } 176 177 protected abstract T createWAL() throws IOException; 178 179 protected abstract void doInit(Configuration conf) throws IOException; 180 181 @Override 182 public void shutdown() throws IOException { 183 T log = this.wal; 184 if (log != null) { 185 log.shutdown(); 186 } 187 } 188 189 @Override 190 public void close() throws IOException { 191 T log = this.wal; 192 if (log != null) { 193 log.close(); 194 } 195 } 196 197 /** 198 * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the 199 * number of files (rolled and active). if either of them aren't, count 0 for that provider. 200 */ 201 @Override 202 public long getNumLogFiles() { 203 T log = this.wal; 204 return log == null ? 0 : log.getNumLogFiles(); 205 } 206 207 /** 208 * iff the given WALFactory is using the DefaultWALProvider for meta and/or non-meta, count the 209 * size of files (only rolled). if either of them aren't, count 0 for that provider. 210 */ 211 @Override 212 public long getLogFileSize() { 213 T log = this.wal; 214 return log == null ? 0 : log.getLogFileSize(); 215 } 216 217 /** 218 * returns the number of rolled WAL files. 219 */ 220 public static int getNumRolledLogFiles(WAL wal) { 221 return ((AbstractFSWAL<?>) wal).getNumRolledLogFiles(); 222 } 223 224 /** 225 * returns the size of rolled WAL files. 226 */ 227 public static long getLogFileSize(WAL wal) { 228 return ((AbstractFSWAL<?>) wal).getLogFileSize(); 229 } 230 231 /** 232 * return the current filename from the current wal. 233 */ 234 public static Path getCurrentFileName(final WAL wal) { 235 return ((AbstractFSWAL<?>) wal).getCurrentFileName(); 236 } 237 238 /** 239 * request a log roll, but don't actually do it. 240 */ 241 static void requestLogRoll(final WAL wal) { 242 ((AbstractFSWAL<?>) wal).requestLogRoll(); 243 } 244 245 // should be package private; more visible for use in AbstractFSWAL 246 public static final String WAL_FILE_NAME_DELIMITER = "."; 247 /** The hbase:meta region's WAL filename extension */ 248 public static final String META_WAL_PROVIDER_ID = ".meta"; 249 static final String DEFAULT_PROVIDER_ID = "default"; 250 251 // Implementation details that currently leak in tests or elsewhere follow 252 /** File Extension used while splitting an WAL into regions (HBASE-2312) */ 253 public static final String SPLITTING_EXT = "-splitting"; 254 255 /** 256 * Pattern used to validate a WAL file name see {@link #validateWALFilename(String)} for 257 * description. 258 */ 259 private static final Pattern WAL_FILE_NAME_PATTERN = 260 Pattern.compile("(.+)\\.(\\d+)(\\.[0-9A-Za-z]+)?"); 261 262 /** 263 * Define for when no timestamp found. 264 */ 265 private static final long NO_TIMESTAMP = -1L; 266 267 /** 268 * It returns the file create timestamp (the 'FileNum') from the file name. For name format see 269 * {@link #validateWALFilename(String)} public until remaining tests move to o.a.h.h.wal 270 * @param wal must not be null 271 * @return the file number that is part of the WAL file name 272 */ 273 public static long extractFileNumFromWAL(final WAL wal) { 274 final Path walPath = ((AbstractFSWAL<?>) wal).getCurrentFileName(); 275 if (walPath == null) { 276 throw new IllegalArgumentException("The WAL path couldn't be null"); 277 } 278 String name = walPath.getName(); 279 long timestamp = getTimestamp(name); 280 if (timestamp == NO_TIMESTAMP) { 281 throw new IllegalArgumentException(name + " is not a valid wal file name"); 282 } 283 return timestamp; 284 } 285 286 /** 287 * A WAL file name is of the format: <wal-name>{@link #WAL_FILE_NAME_DELIMITER} 288 * <file-creation-timestamp>[.<suffix>]. provider-name is usually made up of a 289 * server-name and a provider-id 290 * @param filename name of the file to validate 291 * @return <tt>true</tt> if the filename matches an WAL, <tt>false</tt> otherwise 292 */ 293 public static boolean validateWALFilename(String filename) { 294 return WAL_FILE_NAME_PATTERN.matcher(filename).matches(); 295 } 296 297 /** 298 * Split a WAL filename to get a start time. WALs usually have the time we start writing to them 299 * with as part of their name, usually the suffix. Sometimes there will be an extra suffix as when 300 * it is a WAL for the meta table. For example, WALs might look like this 301 * <code>10.20.20.171%3A60020.1277499063250</code> where <code>1277499063250</code> is the 302 * timestamp. Could also be a meta WAL which adds a '.meta' suffix or a synchronous replication 303 * WAL which adds a '.syncrep' suffix. Check for these. File also may have no timestamp on it. For 304 * example the recovered.edits files are WALs but are named in ascending order. Here is an 305 * example: 0000000000000016310. Allow for this. 306 * @param name Name of the WAL file. 307 * @return Timestamp or {@link #NO_TIMESTAMP}. 308 */ 309 public static long getTimestamp(String name) { 310 Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name); 311 return matcher.matches() ? Long.parseLong(matcher.group(2)) : NO_TIMESTAMP; 312 } 313 314 public static final Comparator<Path> TIMESTAMP_COMPARATOR = 315 Comparator.<Path, Long> comparing(p -> AbstractFSWALProvider.getTimestamp(p.getName())) 316 .thenComparing(Path::getName); 317 318 /** 319 * Construct the directory name for all WALs on a given server. Dir names currently look like this 320 * for WALs: <code>hbase//WALs/kalashnikov.att.net,61634,1486865297088</code>. 321 * @param serverName Server name formatted as described in {@link ServerName} 322 * @return the relative WAL directory name, e.g. <code>.logs/1.example.org,60030,12345</code> if 323 * <code>serverName</code> passed is <code>1.example.org,60030,12345</code> 324 */ 325 public static String getWALDirectoryName(final String serverName) { 326 StringBuilder dirName = new StringBuilder(HConstants.HREGION_LOGDIR_NAME); 327 dirName.append("/"); 328 dirName.append(serverName); 329 return dirName.toString(); 330 } 331 332 /** 333 * Construct the directory name for all old WALs on a given server. The default old WALs dir looks 334 * like: <code>hbase/oldWALs</code>. If you config hbase.separate.oldlogdir.by.regionserver to 335 * true, it looks like <code>hbase//oldWALs/kalashnikov.att.net,61634,1486865297088</code>. 336 * @param serverName Server name formatted as described in {@link ServerName} 337 * @return the relative WAL directory name 338 */ 339 public static String getWALArchiveDirectoryName(Configuration conf, final String serverName) { 340 StringBuilder dirName = new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME); 341 if (conf.getBoolean(SEPARATE_OLDLOGDIR, DEFAULT_SEPARATE_OLDLOGDIR)) { 342 dirName.append(Path.SEPARATOR); 343 dirName.append(serverName); 344 } 345 return dirName.toString(); 346 } 347 348 /** 349 * List all the old wal files for a dead region server. 350 * <p/> 351 * Initially added for supporting replication, where we need to get the wal files to replicate for 352 * a dead region server. 353 */ 354 public static List<Path> getArchivedWALFiles(Configuration conf, ServerName serverName, 355 String logPrefix) throws IOException { 356 Path walRootDir = CommonFSUtils.getWALRootDir(conf); 357 FileSystem fs = walRootDir.getFileSystem(conf); 358 List<Path> archivedWalFiles = new ArrayList<>(); 359 // list both the root old wal dir and the separate old wal dir, so we will not miss any files if 360 // the SEPARATE_OLDLOGDIR config is changed 361 Path oldWalDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); 362 try { 363 for (FileStatus status : fs.listStatus(oldWalDir, p -> p.getName().startsWith(logPrefix))) { 364 if (status.isFile()) { 365 archivedWalFiles.add(status.getPath()); 366 } 367 } 368 } catch (FileNotFoundException e) { 369 LOG.info("Old WAL dir {} not exists", oldWalDir); 370 return Collections.emptyList(); 371 } 372 Path separatedOldWalDir = new Path(oldWalDir, serverName.toString()); 373 try { 374 for (FileStatus status : fs.listStatus(separatedOldWalDir, 375 p -> p.getName().startsWith(logPrefix))) { 376 if (status.isFile()) { 377 archivedWalFiles.add(status.getPath()); 378 } 379 } 380 } catch (FileNotFoundException e) { 381 LOG.info("Seprated old WAL dir {} not exists", separatedOldWalDir); 382 } 383 return archivedWalFiles; 384 } 385 386 /** 387 * List all the wal files for a logPrefix. 388 */ 389 public static List<Path> getWALFiles(Configuration c, ServerName serverName) throws IOException { 390 Path walRoot = new Path(CommonFSUtils.getWALRootDir(c), HConstants.HREGION_LOGDIR_NAME); 391 FileSystem fs = walRoot.getFileSystem(c); 392 List<Path> walFiles = new ArrayList<>(); 393 Path walDir = new Path(walRoot, serverName.toString()); 394 try { 395 for (FileStatus status : fs.listStatus(walDir)) { 396 if (status.isFile()) { 397 walFiles.add(status.getPath()); 398 } 399 } 400 } catch (FileNotFoundException e) { 401 LOG.info("WAL dir {} not exists", walDir); 402 } 403 return walFiles; 404 } 405 406 /** 407 * Pulls a ServerName out of a Path generated according to our layout rules. In the below layouts, 408 * this method ignores the format of the logfile component. Current format: [base directory for 409 * hbase]/hbase/.logs/ServerName/logfile or [base directory for 410 * hbase]/hbase/.logs/ServerName-splitting/logfile Expected to work for individual log files and 411 * server-specific directories. 412 * @return null if it's not a log file. Returns the ServerName of the region server that created 413 * this log file otherwise. 414 */ 415 public static ServerName getServerNameFromWALDirectoryName(Configuration conf, String path) 416 throws IOException { 417 if (path == null || path.length() <= HConstants.HREGION_LOGDIR_NAME.length()) { 418 return null; 419 } 420 421 if (conf == null) { 422 throw new IllegalArgumentException("parameter conf must be set"); 423 } 424 425 final String rootDir = conf.get(HConstants.HBASE_DIR); 426 if (rootDir == null || rootDir.isEmpty()) { 427 throw new IllegalArgumentException(HConstants.HBASE_DIR + " key not found in conf."); 428 } 429 430 final StringBuilder startPathSB = new StringBuilder(rootDir); 431 if (!rootDir.endsWith("/")) { 432 startPathSB.append('/'); 433 } 434 startPathSB.append(HConstants.HREGION_LOGDIR_NAME); 435 if (!HConstants.HREGION_LOGDIR_NAME.endsWith("/")) { 436 startPathSB.append('/'); 437 } 438 final String startPath = startPathSB.toString(); 439 440 String fullPath; 441 try { 442 fullPath = FileSystem.get(conf).makeQualified(new Path(path)).toString(); 443 } catch (IllegalArgumentException e) { 444 LOG.info("Call to makeQualified failed on " + path + " " + e.getMessage()); 445 return null; 446 } 447 448 if (!fullPath.startsWith(startPath)) { 449 return null; 450 } 451 452 final String serverNameAndFile = fullPath.substring(startPath.length()); 453 454 if (serverNameAndFile.indexOf('/') < "a,0,0".length()) { 455 // Either it's a file (not a directory) or it's not a ServerName format 456 return null; 457 } 458 459 Path p = new Path(path); 460 return getServerNameFromWALDirectoryName(p); 461 } 462 463 /** 464 * This function returns region server name from a log file name which is in one of the following 465 * formats: 466 * <ul> 467 * <li>hdfs://<name node>/hbase/.logs/<server name>-splitting/...</li> 468 * <li>hdfs://<name node>/hbase/.logs/<server name>/...</li> 469 * </ul> 470 * @return null if the passed in logFile isn't a valid WAL file path 471 */ 472 public static ServerName getServerNameFromWALDirectoryName(Path logFile) { 473 String logDirName = logFile.getParent().getName(); 474 // We were passed the directory and not a file in it. 475 if (logDirName.equals(HConstants.HREGION_LOGDIR_NAME)) { 476 logDirName = logFile.getName(); 477 } 478 ServerName serverName = null; 479 if (logDirName.endsWith(SPLITTING_EXT)) { 480 logDirName = logDirName.substring(0, logDirName.length() - SPLITTING_EXT.length()); 481 } 482 try { 483 serverName = ServerName.parseServerName(logDirName); 484 } catch (IllegalArgumentException | IllegalStateException ex) { 485 serverName = null; 486 LOG.warn("Cannot parse a server name from path={}", logFile, ex); 487 } 488 if (serverName != null && serverName.getStartCode() < 0) { 489 LOG.warn("Invalid log file path={}, start code {} is less than 0", logFile, 490 serverName.getStartCode()); 491 serverName = null; 492 } 493 return serverName; 494 } 495 496 public static boolean isMetaFile(Path p) { 497 return isMetaFile(p.getName()); 498 } 499 500 /** Returns True if String ends in {@link #META_WAL_PROVIDER_ID} */ 501 public static boolean isMetaFile(String p) { 502 return p != null && p.endsWith(META_WAL_PROVIDER_ID); 503 } 504 505 /** 506 * Comparator used to compare WAL files together based on their start time. Just compares start 507 * times and nothing else. 508 */ 509 public static class WALStartTimeComparator implements Comparator<Path> { 510 @Override 511 public int compare(Path o1, Path o2) { 512 return Long.compare(getTS(o1), getTS(o2)); 513 } 514 515 /** 516 * Split a path to get the start time For example: 10.20.20.171%3A60020.1277499063250 Could also 517 * be a meta WAL which adds a '.meta' suffix or a synchronous replication WAL which adds a 518 * '.syncrep' suffix. Check. 519 * @param p path to split 520 * @return start time 521 */ 522 public static long getTS(Path p) { 523 return getTimestamp(p.getName()); 524 } 525 } 526 527 public static boolean isArchivedLogFile(Path p) { 528 String oldLog = Path.SEPARATOR + HConstants.HREGION_OLDLOGDIR_NAME + Path.SEPARATOR; 529 return p.toString().contains(oldLog); 530 } 531 532 /** 533 * Find the archived WAL file path if it is not able to locate in WALs dir. 534 * @param path - active WAL file path 535 * @param conf - configuration 536 * @return archived path if exists, null - otherwise 537 * @throws IOException exception 538 */ 539 public static Path findArchivedLog(Path path, Configuration conf) throws IOException { 540 // If the path contains oldWALs keyword then exit early. 541 if (path.toString().contains(HConstants.HREGION_OLDLOGDIR_NAME)) { 542 return null; 543 } 544 Path walRootDir = CommonFSUtils.getWALRootDir(conf); 545 FileSystem fs = path.getFileSystem(conf); 546 // Try finding the log in old dir 547 Path oldLogDir = new Path(walRootDir, HConstants.HREGION_OLDLOGDIR_NAME); 548 Path archivedLogLocation = new Path(oldLogDir, path.getName()); 549 if (fs.exists(archivedLogLocation)) { 550 LOG.info("Log " + path + " was moved to " + archivedLogLocation); 551 return archivedLogLocation; 552 } 553 554 ServerName serverName = getServerNameFromWALDirectoryName(path); 555 if (serverName == null) { 556 LOG.warn("Can not extract server name from path {}, " 557 + "give up searching the separated old log dir", path); 558 return null; 559 } 560 // Try finding the log in separate old log dir 561 oldLogDir = new Path(walRootDir, new StringBuilder(HConstants.HREGION_OLDLOGDIR_NAME) 562 .append(Path.SEPARATOR).append(serverName.getServerName()).toString()); 563 archivedLogLocation = new Path(oldLogDir, path.getName()); 564 if (fs.exists(archivedLogLocation)) { 565 LOG.info("Log " + path + " was moved to " + archivedLogLocation); 566 return archivedLogLocation; 567 } 568 LOG.error("Couldn't locate log: " + path); 569 return null; 570 } 571 572 // For HBASE-15019 573 public static void recoverLease(Configuration conf, Path path) { 574 try { 575 final FileSystem dfs = CommonFSUtils.getCurrentFileSystem(conf); 576 RecoverLeaseFSUtils.recoverFileLease(dfs, path, conf, new CancelableProgressable() { 577 @Override 578 public boolean progress() { 579 LOG.debug("Still trying to recover WAL lease: " + path); 580 return true; 581 } 582 }); 583 } catch (IOException e) { 584 LOG.warn("unable to recover lease for WAL: " + path, e); 585 } 586 } 587 588 @Override 589 public void addWALActionsListener(WALActionsListener listener) { 590 listeners.add(listener); 591 } 592 593 private static String getWALNameGroupFromWALName(String name, int group) { 594 Matcher matcher = WAL_FILE_NAME_PATTERN.matcher(name); 595 if (matcher.matches()) { 596 return matcher.group(group); 597 } else { 598 throw new IllegalArgumentException(name + " is not a valid wal file name"); 599 } 600 } 601 602 /** 603 * Get prefix of the log from its name, assuming WAL name in format of 604 * log_prefix.filenumber.log_suffix 605 * @param name Name of the WAL to parse 606 * @return prefix of the log 607 * @throws IllegalArgumentException if the name passed in is not a valid wal file name 608 * @see AbstractFSWAL#getCurrentFileName() 609 */ 610 public static String getWALPrefixFromWALName(String name) { 611 return getWALNameGroupFromWALName(name, 1); 612 } 613 614 private static final Pattern SERVER_NAME_PATTERN = Pattern.compile("^[^" 615 + ServerName.SERVERNAME_SEPARATOR + "]+" + ServerName.SERVERNAME_SEPARATOR 616 + Addressing.VALID_PORT_REGEX + ServerName.SERVERNAME_SEPARATOR + Addressing.VALID_PORT_REGEX); 617 618 /** 619 * Parse the server name from wal prefix. A wal's name is always started with a server name in non 620 * test code. 621 * @throws IllegalArgumentException if the name passed in is not started with a server name 622 * @return the server name 623 */ 624 public static ServerName parseServerNameFromWALName(String name) { 625 String decoded; 626 try { 627 decoded = URLDecoder.decode(name, StandardCharsets.UTF_8.name()); 628 } catch (UnsupportedEncodingException e) { 629 throw new AssertionError("should never happen", e); 630 } 631 Matcher matcher = SERVER_NAME_PATTERN.matcher(decoded); 632 if (matcher.find()) { 633 return ServerName.valueOf(matcher.group()); 634 } else { 635 throw new IllegalArgumentException(name + " is not started with a server name"); 636 } 637 } 638}