/* * 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.hadoop.registry.client.binding; import com.google.common.base.Preconditions; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.fs.PathNotFoundException; import org.apache.hadoop.registry.client.exceptions.InvalidPathnameException; import org.apache.hadoop.registry.client.impl.zk.RegistryInternalConstants; import org.apache.zookeeper.common.PathUtils; import java.net.IDN; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; /** * Basic operations on paths: manipulating them and creating and validating * path elements. */ @InterfaceAudience.Private @InterfaceStability.Evolving public class RegistryPathUtils { /** * Compiled down pattern to validate single entries in the path */ private static final Pattern PATH_ENTRY_VALIDATION_PATTERN = Pattern.compile(RegistryInternalConstants.VALID_PATH_ENTRY_PATTERN); /** * Validate ZK path with the path itself included in * the exception text * @param path path to validate * @return the path parameter * @throws InvalidPathnameException if the pathname is invalid. */ public static String validateZKPath(String path) throws InvalidPathnameException { try { PathUtils.validatePath(path); } catch (IllegalArgumentException e) { throw new InvalidPathnameException(path, "Invalid Path \"" + path + "\" : " + e, e); } return path; } /** * Validate ZK path as valid for a DNS hostname. * @param path path to validate * @return the path parameter * @throws InvalidPathnameException if the pathname is invalid. */ public static String validateElementsAsDNS(String path) throws InvalidPathnameException { List<String> splitpath = split(path); for (String fragment : splitpath) { if (!PATH_ENTRY_VALIDATION_PATTERN.matcher(fragment).matches()) { throw new InvalidPathnameException(path, "Invalid Path element \"" + fragment + "\""); } } return path; } /** * Create a full path from the registry root and the supplied subdir * @param path path of operation * @return an absolute path * @throws InvalidPathnameException if the path is invalid */ public static String createFullPath(String base, String path) throws InvalidPathnameException { Preconditions.checkArgument(path != null, "null path"); Preconditions.checkArgument(base != null, "null path"); return validateZKPath(join(base, path)); } /** * Join two paths, guaranteeing that there will not be exactly * one separator between the two, and exactly one at the front * of the path. There will be no trailing "/" except for the special * case that this is the root path * @param base base path * @param path second path to add * @return a combined path. */ public static String join(String base, String path) { Preconditions.checkArgument(path != null, "null path"); Preconditions.checkArgument(base != null, "null path"); StringBuilder fullpath = new StringBuilder(); if (!base.startsWith("/")) { fullpath.append('/'); } fullpath.append(base); // guarantee a trailing / if (!fullpath.toString().endsWith("/")) { fullpath.append("/"); } // strip off any at the beginning if (path.startsWith("/")) { // path starts with /, so append all other characters -if present if (path.length() > 1) { fullpath.append(path.substring(1)); } } else { fullpath.append(path); } //here there may be a trailing "/" String finalpath = fullpath.toString(); if (finalpath.endsWith("/") && !"/".equals(finalpath)) { finalpath = finalpath.substring(0, finalpath.length() - 1); } return finalpath; } /** * split a path into elements, stripping empty elements * @param path the path * @return the split path */ public static List<String> split(String path) { // String[] pathelements = path.split("/"); List<String> dirs = new ArrayList<String>(pathelements.length); for (String pathelement : pathelements) { if (!pathelement.isEmpty()) { dirs.add(pathelement); } } return dirs; } /** * Get the last entry in a path; for an empty path * returns "". The split logic is that of * {@link #split(String)} * @param path path of operation * @return the last path entry or "" if none. */ public static String lastPathEntry(String path) { List<String> splits = split(path); if (splits.isEmpty()) { // empty path. Return "" return ""; } else { return splits.get(splits.size() - 1); } } /** * Get the parent of a path * @param path path to look at * @return the parent path * @throws PathNotFoundException if the path was at root. */ public static String parentOf(String path) throws PathNotFoundException { List<String> elements = split(path); int size = elements.size(); if (size == 0) { throw new PathNotFoundException("No parent of " + path); } if (size == 1) { return "/"; } elements.remove(size - 1); StringBuilder parent = new StringBuilder(path.length()); for (String element : elements) { parent.append("/"); parent.append(element); } return parent.toString(); } /** * Perform any formatting for the registry needed to convert * non-simple-DNS elements * @param element element to encode * @return an encoded string */ public static String encodeForRegistry(String element) { return IDN.toASCII(element); } /** * Perform whatever transforms are needed to get a YARN ID into * a DNS-compatible name * @param yarnId ID as string of YARN application, instance or container * @return a string suitable for use in registry paths. */ public static String encodeYarnID(String yarnId) { return yarnId.replace("_", "-"); } }