/*
* 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 gobblin.config.client;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.hadoop.fs.Path;
import com.google.common.base.Preconditions;
import gobblin.config.common.impl.SingleLinkedListConfigKeyPath;
import gobblin.config.store.api.ConfigKeyPath;
import gobblin.config.store.api.ConfigStore;
/**
* Utility class to transfer {@link URI} to {@link ConfigKeyPath} and vice versa
*
* @author mitu
*
*/
public class ConfigClientUtils {
/**
*
* @param configKeyURI - URI provided by client , which could missing authority/store root directory
* @param cs - ConfigStore corresponding to the input URI. Require input URI's scheme/authority name
* match ConfigStore's scheme/authority
* @return - {@link ConfigKeyPath} for the relative path
*/
public static ConfigKeyPath buildConfigKeyPath(URI configKeyURI, ConfigStore cs) {
checkMatchingSchemeAndAuthority(configKeyURI, cs);
// Example store root is etl-hdfs://eat1-nertznn01.grid.linkedin.com:9000/user/mitu/HdfsBasedConfigTest
URI relative = cs.getStoreURI().relativize(configKeyURI);
return getConfigKeyPath(relative.getPath());
}
/**
* Build the URI based on the {@link ConfigStore} or input cnofigKeyURI
*
* @param configKeyPath : relative path to the input config store cs
* @param returnURIWithAuthority : return the URI with input config store's authority and absolute path
* @param cs : the config store of the input configKeyURI
* @return : return the URI of the same format with the input configKeyURI
*
* for example, configKeyPath is /tags/retention,
* with returnURIWithAuthority as true, return "etl-hdfs:///tags/retention
* with returnURIWithAuthority as false , then return
* etl-hdfs://eat1-nertznn01.grid.linkedin.com:9000/user/mitu/HdfsBasedConfigTest/tags/retention
*/
public static URI buildUriInClientFormat(ConfigKeyPath configKeyPath, ConfigStore cs,
boolean returnURIWithAuthority) {
try {
if (!returnURIWithAuthority) {
return new URI(cs.getStoreURI().getScheme(), null, configKeyPath.getAbsolutePathString(), null, null);
}
URI storeRoot = cs.getStoreURI();
// if configKeyPath is root, the configKeyPath.getAbsolutePathString().substring(1) will return "" and
// will cause the Path creation failure if not handled here
if (configKeyPath.isRootPath()) {
return storeRoot;
}
Path absPath = new Path(storeRoot.getPath(), configKeyPath.getAbsolutePathString().substring(1)); // remote the first "/";
return new URI(storeRoot.getScheme(), storeRoot.getAuthority(), absPath.toString(), null, null);
} catch (URISyntaxException e) {
// should not come here
throw new RuntimeException("Can not build URI based on " + configKeyPath);
}
}
public static Collection<URI> buildUriInClientFormat(Collection<ConfigKeyPath> configKeyPaths, ConfigStore cs,
boolean returnURIWithAuthority) {
Collection<URI> result = new ArrayList<>();
if (configKeyPaths == null) {
return result;
}
for (ConfigKeyPath p : configKeyPaths) {
result.add(buildUriInClientFormat(p, cs, returnURIWithAuthority));
}
return result;
}
/**
* Build the {@link ConfigKeyPath} based on the absolute/relative path
* @param input - absolute/relative file path
* @return - {@link ConfigKeyPath} corresponding to the input
*/
public static ConfigKeyPath getConfigKeyPath(String input) {
ConfigKeyPath result = SingleLinkedListConfigKeyPath.ROOT;
String[] paths = input.split("/");
for (String p : paths) {
// in case input start with "/", some elements could be "", which should be skip
if (p.equals("")) {
continue;
}
result = result.createChild(p);
}
return result;
}
public static List<ConfigKeyPath> getConfigKeyPath(List<String> input) {
List<ConfigKeyPath> result = new ArrayList<>();
for (String s : input) {
result.add(getConfigKeyPath(s));
}
return result;
}
private static void checkMatchingSchemeAndAuthority(URI configKeyURI, ConfigStore cs) {
Preconditions.checkNotNull(configKeyURI, "input can not be null");
Preconditions.checkNotNull(cs, "input can not be null");
Preconditions.checkArgument(configKeyURI.getScheme().equals(cs.getStoreURI().getScheme()), "Scheme name not match");
boolean authorityCheck =
configKeyURI.getAuthority() == null || configKeyURI.getAuthority().equals(cs.getStoreURI().getAuthority());
Preconditions.checkArgument(authorityCheck, "Authority not match");
}
/**
* Utility method to check whether one URI is the ancestor of the other
*
* return true iff both URI's scheme/authority name match and ancestor's path is the prefix of the descendant's path
* @param descendant: the descendant URI to check
* @param ancestor : the ancestor URI to check
* @return
*/
public static boolean isAncestorOrSame(URI descendant, URI ancestor) {
Preconditions.checkNotNull(descendant, "input can not be null");
Preconditions.checkNotNull(ancestor, "input can not be null");
if (!stringSame(descendant.getScheme(), ancestor.getScheme())) {
return false;
}
if (!stringSame(descendant.getAuthority(), ancestor.getAuthority())) {
return false;
}
return isAncestorOrSame(getConfigKeyPath(descendant.getPath()), getConfigKeyPath(ancestor.getPath()));
}
public static boolean stringSame(String l, String r) {
if (l == null && r == null) {
return true;
}
if (l == null || r == null) {
return false;
}
return l.equals(r);
}
public static boolean isAncestorOrSame(ConfigKeyPath descendant, ConfigKeyPath ancestor) {
Preconditions.checkNotNull(descendant, "input can not be null");
Preconditions.checkNotNull(ancestor, "input can not be null");
if (descendant.equals(ancestor))
return true;
if (descendant.isRootPath())
return false;
return isAncestorOrSame(descendant.getParent(), ancestor);
}
}