/*
* 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.restli;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.linkedin.r2.filter.FilterChains;
import com.linkedin.r2.transport.common.Client;
import com.linkedin.r2.transport.common.bridge.client.TransportClientAdapter;
import com.linkedin.r2.transport.http.client.HttpClientFactory;
import com.linkedin.restli.client.RestClient;
import com.typesafe.config.Config;
import gobblin.broker.ResourceCoordinate;
import gobblin.broker.ResourceInstance;
import gobblin.broker.iface.ConfigView;
import gobblin.broker.iface.NotConfiguredException;
import gobblin.broker.iface.ScopeType;
import gobblin.broker.iface.ScopedConfigView;
import gobblin.broker.iface.SharedResourceFactory;
import gobblin.broker.iface.SharedResourceFactoryResponse;
import gobblin.broker.iface.SharedResourcesBroker;
import gobblin.util.ExecutorsUtils;
import io.netty.channel.nio.NioEventLoopGroup;
/**
* A {@link SharedResourceFactory} to create {@link RestClient}s.
*
* To configure, specify rest server uri at key "serverUri". Note uri must start with "http" or "https".
*/
public class SharedRestClientFactory<S extends ScopeType<S>> implements SharedResourceFactory<RestClient, SharedRestClientKey, S> {
public static final String FACTORY_NAME = "restli";
public static final String SERVER_URI_KEY = "serverUri";
private static final Set<String> RESTLI_SCHEMES = Sets.newHashSet("http", "https");
@Override
public String getName() {
return FACTORY_NAME;
}
@Override
public SharedResourceFactoryResponse<RestClient>
createResource(SharedResourcesBroker<S> broker, ScopedConfigView<S, SharedRestClientKey> config) throws NotConfiguredException {
try {
SharedRestClientKey key = config.getKey();
if (!(key instanceof UriRestClientKey)) {
return new ResourceCoordinate<>(this, new UriRestClientKey(key.serviceName, resolveUriPrefix(config.getConfig(), key)),
config.getScope());
}
String uriPrefix = ((UriRestClientKey) key).getUri();
HttpClientFactory http = new HttpClientFactory(FilterChains.empty(),
new NioEventLoopGroup(0 /* use default settings */,
ExecutorsUtils.newDaemonThreadFactory(Optional.<Logger>absent(), Optional.of("R2 Nio Event Loop-%d"))),
true,
Executors.newSingleThreadScheduledExecutor(
ExecutorsUtils.newDaemonThreadFactory(Optional.<Logger>absent(), Optional.of("R2 Netty Scheduler"))),
true);
Client r2Client = new TransportClientAdapter(http.getClient(Collections.<String, String>emptyMap()));
return new ResourceInstance<>(new RestClient(r2Client,uriPrefix));
} catch (URISyntaxException use) {
throw new RuntimeException("Could not create a rest client for key " + Optional.fromNullable(config.getKey().toConfigurationKey()).or("null"));
}
}
@Override
public S getAutoScope(SharedResourcesBroker<S> broker, ConfigView<S, SharedRestClientKey> config) {
return broker.selfScope().getType().rootScope();
}
/**
* Get a uri prefix from the input configuration.
*/
public static String resolveUriPrefix(Config config, SharedRestClientKey key) throws URISyntaxException, NotConfiguredException {
List<String> connectionPrefixes = parseConnectionPrefixes(config, key);
Preconditions.checkArgument(connectionPrefixes.size() > 0, "No uris found for service " + key.serviceName);
return connectionPrefixes.get(new Random().nextInt(connectionPrefixes.size()));
}
/**
* Parse the list of available input prefixes from the input configuration.
*/
public static List<String> parseConnectionPrefixes(Config config, SharedRestClientKey key) throws URISyntaxException, NotConfiguredException {
if (key instanceof UriRestClientKey) {
return Lists.newArrayList(((UriRestClientKey) key).getUri());
}
if (!config.hasPath(SERVER_URI_KEY)) {
throw new NotConfiguredException("Missing key " + SERVER_URI_KEY);
}
List<String> uris = Lists.newArrayList();
for (String uri : Splitter.on(",").omitEmptyStrings().trimResults().splitToList(config.getString(SERVER_URI_KEY))) {
uris.add(resolveUriPrefix(new URI(uri)));
}
return uris;
}
/**
* Convert the input URI into a correctly formatted uri prefix. In the future, may also resolve d2 uris.
*/
public static String resolveUriPrefix(URI serverURI)
throws URISyntaxException {
if (RESTLI_SCHEMES.contains(serverURI.getScheme())) {
return new URI(serverURI.getScheme(), serverURI.getAuthority(), null, null, null).toString() + "/";
}
throw new RuntimeException("Unrecognized scheme for URI " + serverURI);
}
}