/* * 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.ranger.services.schema.registry.client.connection; import com.hortonworks.registries.auth.Login; import com.hortonworks.registries.schemaregistry.client.LoadBalancedFailoverUrlSelector; import com.hortonworks.registries.schemaregistry.client.UrlSelector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ranger.services.schema.registry.client.connection.util.SecurityUtils; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONObject; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; import org.glassfish.jersey.client.JerseyClientBuilder; import javax.net.ssl.SSLContext; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response;; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.hortonworks.registries.schemaregistry.client.SchemaRegistryClient.Configuration; import static com.hortonworks.registries.schemaregistry.client.SchemaRegistryClient.Configuration.DEFAULT_CONNECTION_TIMEOUT; import static com.hortonworks.registries.schemaregistry.client.SchemaRegistryClient.Configuration.DEFAULT_READ_TIMEOUT; public class DefaultSchemaRegistryClient implements ISchemaRegistryClient { private static final Log LOG = LogFactory.getLog(DefaultSchemaRegistryClient.class); private static final String SCHEMA_REGISTRY_PATH = "/api/v1/schemaregistry"; private static final String SCHEMAS_PATH = SCHEMA_REGISTRY_PATH + "/schemas/"; private static final String SCHEMA_REGISTRY_VERSION_PATH = SCHEMA_REGISTRY_PATH + "/version"; private static final String SSL_ALGORITHM = "TLS"; private final javax.ws.rs.client.Client client; private final Login login; private final UrlSelector urlSelector; private final Map<String, SchemaRegistryTargets> urlWithTargets; private final Configuration configuration; public DefaultSchemaRegistryClient(Map<String, ?> conf) { configuration = new Configuration(conf); login = SecurityUtils.initializeSecurityContext(conf); ClientConfig config = createClientConfig(conf); final boolean SSLEnabled = SecurityUtils.isHttpsConnection(conf); ClientBuilder clientBuilder = JerseyClientBuilder.newBuilder() .withConfig(config) .property(ClientProperties.FOLLOW_REDIRECTS, Boolean.TRUE); if (SSLEnabled) { SSLContext ctx; try { ctx = SecurityUtils.createSSLContext(conf, SSL_ALGORITHM); } catch (Exception e) { throw new RuntimeException(e); } clientBuilder.sslContext(ctx); } client = clientBuilder.build(); // get list of urls and create given or default UrlSelector. urlSelector = createUrlSelector(); urlWithTargets = new ConcurrentHashMap<>(); } private ClientConfig createClientConfig(Map<String, ?> conf) { ClientConfig config = new ClientConfig(); config.property(ClientProperties.CONNECT_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); config.property(ClientProperties.READ_TIMEOUT, DEFAULT_READ_TIMEOUT); config.property(ClientProperties.FOLLOW_REDIRECTS, true); for (Map.Entry<String, ?> entry : conf.entrySet()) { config.property(entry.getKey(), entry.getValue()); } return config; } private UrlSelector createUrlSelector() { UrlSelector urlSelector = null; String rootCatalogURL = configuration.getValue(Configuration.SCHEMA_REGISTRY_URL.name()); String urlSelectorClass = configuration.getValue(Configuration.URL_SELECTOR_CLASS.name()); if (urlSelectorClass == null) { urlSelector = new LoadBalancedFailoverUrlSelector(rootCatalogURL); } else { try { urlSelector = (UrlSelector) Class.forName(urlSelectorClass) .getConstructor(String.class) .newInstance(rootCatalogURL); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException(e); } } urlSelector.init(configuration.getConfig()); return urlSelector; } private static class SchemaRegistryTargets { private final WebTarget schemaRegistryVersion; private final WebTarget schemasTarget; SchemaRegistryTargets(WebTarget rootResource) { schemaRegistryVersion = rootResource.path(SCHEMA_REGISTRY_VERSION_PATH); schemasTarget = rootResource.path(SCHEMAS_PATH); } } private SchemaRegistryTargets currentSchemaRegistryTargets() { String url = urlSelector.select(); urlWithTargets.computeIfAbsent(url, s -> new SchemaRegistryTargets(client.target(s))); return urlWithTargets.get(url); } private static String encode(String schemaName) { try { return URLEncoder.encode(schemaName, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @Override public List<String> getSchemaGroups() { if(LOG.isDebugEnabled()) { LOG.debug("==> DefaultSchemaRegistryClient.getSchemaGroups()"); } ArrayList<String> res = new ArrayList<>(); WebTarget webResource = currentSchemaRegistryTargets().schemasTarget; try { Response response = login.doAction(() -> webResource.request(MediaType.APPLICATION_JSON_TYPE).get(Response.class)); if(LOG.isDebugEnabled()) { LOG.debug("DefaultSchemaRegistryClient.getSchemaGroups(): response statusCode = " + response.getStatus()); } JSONArray mDataList = new JSONObject(response.readEntity(String.class)).getJSONArray("entities"); int len = mDataList.length(); for(int i = 0; i < len; i++) { JSONObject entity = mDataList.getJSONObject(i); JSONObject schemaMetadata = (JSONObject)entity.get("schemaMetadata"); String group = (String) schemaMetadata.get("schemaGroup"); res.add(group); } } catch (Exception e) { throw new RuntimeException(e); } if(LOG.isDebugEnabled()) { LOG.debug("<== DefaultSchemaRegistryClient.getSchemaGroups(): " + res.size() + " schemaGroups found"); } return res; } @Override public List<String> getSchemaNames(List<String> schemaGroups) { if(LOG.isDebugEnabled()) { LOG.debug("==> DefaultSchemaRegistryClient.getSchemaNames( " + schemaGroups + " )"); } ArrayList<String> res = new ArrayList<>(); WebTarget webTarget = currentSchemaRegistryTargets().schemasTarget; try { Response response = login.doAction(() -> webTarget.request(MediaType.APPLICATION_JSON_TYPE).get(Response.class)); if(LOG.isDebugEnabled()) { LOG.debug("DefaultSchemaRegistryClient.getSchemaNames(): response statusCode = " + response.getStatus()); } JSONArray mDataList = new JSONObject(response.readEntity(String.class)).getJSONArray("entities"); int len = mDataList.length(); for(int i = 0; i < len; i++) { JSONObject entity = mDataList.getJSONObject(i); JSONObject schemaMetadata = (JSONObject)entity.get("schemaMetadata"); String group = (String) schemaMetadata.get("schemaGroup"); for(String schemaGroup: schemaGroups) { if(group.matches(schemaGroup)) { String name = (String) schemaMetadata.get("name"); res.add(name); } } } } catch (Exception e) { throw new RuntimeException(e); } if(LOG.isDebugEnabled()) { LOG.debug("<== DefaultSchemaRegistryClient.getSchemaNames( " + schemaGroups + " ): " + res.size() + " schemaNames found"); } return res; } @Override public List<String> getSchemaBranches(String schemaMetadataName) { if(LOG.isDebugEnabled()) { LOG.debug("==> DefaultSchemaRegistryClient.getSchemaBranches( " + schemaMetadataName + " )"); } ArrayList<String> res = new ArrayList<>(); WebTarget target = currentSchemaRegistryTargets().schemasTarget.path(encode(schemaMetadataName) + "/branches"); try { Response response = login.doAction(() -> target.request(MediaType.APPLICATION_JSON_TYPE).get(Response.class)); if(LOG.isDebugEnabled()) { LOG.debug("DefaultSchemaRegistryClient.getSchemaBranches(): response statusCode = " + response.getStatus()); } JSONArray mDataList = new JSONObject(response.readEntity(String.class)).getJSONArray("entities"); int len = mDataList.length(); for(int i = 0; i < len; i++) { JSONObject entity = mDataList.getJSONObject(i); JSONObject branchInfo = entity; String smName = (String) branchInfo.get("schemaMetadataName"); if (smName.matches(schemaMetadataName)) { String bName = (String) branchInfo.get("name"); res.add(bName); } } } catch (Exception e) { throw new RuntimeException(e); } if(LOG.isDebugEnabled()) { LOG.debug("<== DefaultSchemaRegistryClient.getSchemaBranches( " + schemaMetadataName + " ): " + res.size() + " branches found."); } return res; } @Override public void checkConnection() throws Exception { if(LOG.isDebugEnabled()) { LOG.debug("==> DefaultSchemaRegistryClient.checkConnection(): trying to connect to the SR server... "); } WebTarget webTarget = currentSchemaRegistryTargets().schemaRegistryVersion; Response responce = login.doAction(() -> webTarget.request(MediaType.APPLICATION_JSON_TYPE).get(Response.class)); if(LOG.isDebugEnabled()) { LOG.debug("DefaultSchemaRegistryClient.checkConnection(): response statusCode = " + responce.getStatus()); } if(responce.getStatus() != Response.Status.OK.getStatusCode()) { LOG.error("DefaultSchemaRegistryClient.checkConnection(): Connection failed. Response StatusCode = " + responce.getStatus()); throw new Exception("Connection failed. StatusCode = " + responce.getStatus()); } String respStr = responce.readEntity(String.class); if (!(respStr.contains("version") && respStr.contains("revision"))) { LOG.error("DefaultSchemaRegistryClient.checkConnection(): Connection failed. Bad response body."); throw new Exception("Connection failed. Bad response body."); } if(LOG.isDebugEnabled()) { LOG.debug("<== DefaultSchemaRegistryClient.checkConnection(): connection test successfull "); } } }