Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.groupbyinc.api;
import com.groupbyinc.api.config.ConnectionConfiguration;
import com.groupbyinc.api.model.RefinementsResult;
import com.groupbyinc.api.model.Results;
import com.groupbyinc.api.request.RefinementsRequest;
import com.groupbyinc.api.request.Request;
import com.groupbyinc.common.apache.commons.collections4.MapUtils;
import com.groupbyinc.common.apache.commons.lang3.StringUtils;
import com.groupbyinc.common.apache.http.ConnectionClosedException;
import com.groupbyinc.common.apache.http.Header;
import com.groupbyinc.common.apache.http.HttpEntity;
import com.groupbyinc.common.apache.http.NoHttpResponseException;
import com.groupbyinc.common.apache.http.StatusLine;
import com.groupbyinc.common.apache.http.client.config.RequestConfig;
import com.groupbyinc.common.apache.http.client.methods.CloseableHttpResponse;
import com.groupbyinc.common.apache.http.client.methods.HttpPost;
import com.groupbyinc.common.apache.http.client.utils.URIBuilder;
import com.groupbyinc.common.apache.http.entity.StringEntity;
import com.groupbyinc.common.apache.http.impl.client.CloseableHttpClient;
import com.groupbyinc.common.apache.http.impl.client.HttpClientBuilder;
import com.groupbyinc.common.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import com.groupbyinc.common.apache.http.message.BasicHeader;
import com.groupbyinc.common.apache.http.util.EntityUtils;
import com.groupbyinc.common.jackson.Mappers;
import com.groupbyinc.common.security.AesContent;
import com.groupbyinc.common.security.AesEncryption;
import com.groupbyinc.common.util.ThreadUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
*
* The Bridge is the class responsible for marshalling a query to and from the search service.
* Because the bridge holds a connection pool that is expensive to create, it is highly recommended
* that the bridge is held in the application memory scope and reused where appropriate.
* Do not create a new bridge object for each request as you will incur overhead that will
* bring down your UI servers when under heavy load!
*
*/
public abstract class AbstractBridge {
private static final Logger LOG = Logger.getLogger(AbstractBridge.class.getName());
public static final int DEFAULT_RETRY_TIMEOUT = 80;
public static final int DEFAULT_MAX_TRIES = 3;
public static final String CLUSTER = "/cluster";
protected static final String COLON = ":";
protected static final String HTTP = "http://";
protected static final String HTTPS = "https://";
private static final String SEARCH = "/search";
private static final String REFINEMENTS = "/refinements";
private static final String BODY = "\nbody:\n";
private static final String EXCEPTION_FROM_BRIDGE = "Exception from bridge: ";
public static final Charset UTF_8 = Charset.forName("UTF-8");
private final ConnectionConfiguration config;
private final RequestConfig requestConfig;
private final String bridgeUrl;
private final String bridgeRefinementsUrl;
private final String bridgeClusterUrl;
protected String clientKey;
private CloseableHttpClient httpClient;
private long retryTimeout = DEFAULT_RETRY_TIMEOUT;
private long maxTries = DEFAULT_MAX_TRIES;
private volatile List headers = new ArrayList();
private ScheduledExecutorService idleConnectionMonitor = Executors.newSingleThreadScheduledExecutor(ThreadUtils.defaultThreadFactory("idle-connections", false, false));
/**
*
* Constructor to create a bridge object that connects to the search api.
*
* JSON Reference:
* The key as found in your key management page in the command center
*
* {"clientKey": ""}
*
*
*
* @param clientKey
* The key as found in your key management page in the command
* center.
* @param baseUrl
* The base url the bridge is serving on.
*/
public AbstractBridge(String clientKey, String baseUrl) {
this(clientKey, baseUrl, true, new ConnectionConfiguration());
}
/**
*
* Constructor to create a bridge object that connects to the search api.
*
* JSON Reference:
* The key as found in your key management page in the command center
*
* {"clientKey": ""}
*
*
*
* @param clientKey
* The key as found in your key management page in the command
* center.
* @param baseUrl
* The base url the bridge is serving on.
* @param compressResponse
* true to compress the response content, false to send uncompressed response.
* @param config
* Configuration for the underlying HttpClient instance.
*/
public AbstractBridge(String clientKey, String baseUrl, boolean compressResponse, ConnectionConfiguration config) {
try {
new URI(baseUrl);
} catch (URISyntaxException e) {
throw new IllegalStateException("Invalid url: " + baseUrl);
}
this.config = config;
requestConfig =
RequestConfig.custom().setConnectTimeout(config.getConnectTimeout()).setConnectionRequestTimeout(config.getConnectionRequestTimeout()).setSocketTimeout(config.getSocketTimeout()).build();
this.clientKey = clientKey;
createClient(compressResponse);
bridgeUrl = baseUrl + SEARCH;
bridgeRefinementsUrl = bridgeUrl + REFINEMENTS;
bridgeClusterUrl = baseUrl + CLUSTER;
}
private void createClient(boolean compressResponse) {
final PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(config.getMaxConnections());
cm.setDefaultMaxPerRoute(config.getMaxConnectionsPerRoute());
HttpClientBuilder b = HttpClientBuilder.create();
if (!compressResponse) {
b.disableContentCompression();
}
httpClient = b.setConnectionManager(cm).setDefaultRequestConfig(requestConfig).build();
idleConnectionMonitor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
cm.closeExpiredConnections();
cm.closeIdleConnections(20, TimeUnit.SECONDS);
}
}, 10, 30, TimeUnit.SECONDS);
}
/**
*
* Constructor to create a bridge object that connects to the search api.
*
* JSON Reference:
* The key as found in your key management page in the command center
*
* {"clientKey": ""}
*
*
*
* @param clientKey
* The key as found in your key management page in the command
* center.
* @param baseUrl
* The base url the bridge is serving on.
* @param config
* Configuration for the underlying HttpClient instance.
*/
public AbstractBridge(String clientKey, String baseUrl, ConnectionConfiguration config) {
this(clientKey, baseUrl, true, config);
}
/**
*
* Constructor to create a bridge object that connects to the search api.
*
* JSON Reference:
* The key as found in your key management page in the command center
*
* {"clientKey": ""}
*
*
*
* @param clientKey
* The key as found in your key management page in the command
* center.
* @param baseUrl
* The base url the bridge is serving on.
* @param compressResponse
* true to compress the response content, false to send uncompressed response.
*/
public AbstractBridge(String clientKey, String baseUrl, boolean compressResponse) {
this(clientKey, baseUrl, compressResponse, new ConnectionConfiguration());
}
/**
* @internal
*/
public String getClusterBridgeUrl() {
return bridgeClusterUrl;
}
/**
*
* Connects to the search service, parses the response into a model
*
*
* @param query
* A query representing the search.
*
* @return Results object from the search service
*/
public Results search(Query query) throws IOException {
return search(clientKey, Collections.emptyMap(), query);
}
protected Results search(String clientKey, Map headers, Query query) throws IOException {
InputStream data = fireRequest(getBridgeUrl(), query.getQueryUrlParams(), query.getBridgeJson(clientKey), query.isReturnBinary());
return map(data, query.isReturnBinary());
}
protected InputStream fireRequest(String url, Map urlParams, String body, boolean returnBinary) throws IOException {
return fireRequest(url, urlParams, Collections.emptyMap(), body, returnBinary);
}
protected InputStream fireRequest(String url, Map urlParams, Map headers, String body, boolean returnBinary) throws IOException {
CloseableHttpResponse response = postToBridge(url, urlParams, headers, body);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() == 200) {
return entity.getContent();
} else {
String status = response.getStatusLine().toString();
handleErrorStatus(status, EntityUtils.toByteArray(entity), returnBinary);
return null;
}
}
/**
* @internal
*/
public String getBridgeUrl() {
return bridgeUrl;
}
protected Results map(InputStream data, boolean returnBinary) {
return Mappers.readValue(data, Results.class, returnBinary);
}
protected static void addHeader(List headers, String key, String value) {
removeHeader(headers, key);
headers.add(new BasicHeader(key, value));
}
protected static void removeHeader(List headers, String key) {
Iterator iterator = headers.iterator();
while (iterator.hasNext()) {
Header header = iterator.next();
if (header.getName().equalsIgnoreCase(key)) {
iterator.remove();
}
}
}
protected static boolean containsHeader(List headers, String key) {
for (Header header : headers) {
if (header.getName().equalsIgnoreCase(key)) {
return true;
}
}
return false;
}
private CloseableHttpResponse postToBridge(String url, Map urlParams, Map headers, String bridgeJson) throws IOException {
StringEntity entity = new StringEntity(bridgeJson, UTF_8);
entity.setContentType("application/json");
CloseableHttpResponse response = null;
boolean successful = false;
int tries = 0;
Exception lastError = null;
List finalHeaders = new ArrayList();
finalHeaders.addAll(this.headers);
for (Map.Entry header : headers.entrySet()) {
addHeader(finalHeaders, header.getKey(), header.getValue());
}
while (!successful && tries < maxTries) {
try {
HttpPost httpPost = new HttpPost(generateURI(url, urlParams, tries));
for (Header header : finalHeaders) {
httpPost.addHeader(header);
}
httpPost.setEntity(entity);
response = httpClient.execute(httpPost);
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() == 502) {
ThreadUtils.sleep(retryTimeout);
LOG.warning("Connection failed, retrying");
lastError = new IOException(statusLine.getReasonPhrase());
tries++;
} else {
successful = true;
}
} catch (URISyntaxException e) {
LOG.severe("Invalid request, failing");
break;
} catch (SocketException e) {
ThreadUtils.sleep(retryTimeout);
LOG.warning("Connection failed, retrying");
lastError = e;
tries++;
} catch (ConnectionClosedException e) {
ThreadUtils.sleep(retryTimeout);
LOG.warning("Connection failed, retrying");
lastError = e;
tries++;
} catch (NoHttpResponseException e) {
ThreadUtils.sleep(retryTimeout);
LOG.warning("Connection failed, retrying");
lastError = e;
tries++;
}
}
if (tries < maxTries) {
return response;
}
throw new IOException("Tried to connect three times to: " + url, lastError);
}
protected void handleErrorStatus(String status, byte[] bytes, boolean returnBinary) throws IOException {
StringBuilder msg = new StringBuilder();
try {
String errors = map(new ByteArrayInputStream(bytes), returnBinary).getErrors();
if (StringUtils.isNotBlank(errors)) {
msg.append(", ").append(errors);
}
} catch (Exception e) {
LOG.warning("unable to parse error from response.");
} finally {
if (StringUtils.isBlank(msg)) {
msg.append(BODY).append(StringUtils.toEncodedString(bytes, UTF_8));
}
}
throw new IOException(EXCEPTION_FROM_BRIDGE + status + msg.toString());
}
protected URI generateURI(String url, Map params, int tries) throws URISyntaxException {
URIBuilder u = new URIBuilder(url);
if (MapUtils.isNotEmpty(params)) {
for (Map.Entry e : params.entrySet()) {
u.addParameter(e.getKey(), e.getValue());
}
}
u.addParameter("retry", Integer.toString(tries));
return u.build();
}
/**
* @internal
* using the request object instead of the query object.
*/
public Results search(Request request) throws IOException {
return search(request, Collections.emptyMap());
}
/**
* @internal
* using the request object instead of the query object.
*/
public Results search(Request request, Map headers) throws IOException {
makeBackwardsCompatible(request);
String json = getJson(request);
Boolean returnBinary = request.getReturnBinary() == null ? false : request.getReturnBinary();
InputStream data = fireRequest(getBridgeUrl(), request.getQueryUrlParams(), headers, json, returnBinary);
return map(data, returnBinary);
}
private void makeBackwardsCompatible(Request request) {
if (StringUtils.isEmpty(request.getClientKey())) {
request.setClientKey(clientKey);
}
if (request.getSkip() == null) {
request.setSkip(0);
}
if (request.getPageSize() == null) {
request.setPageSize(10);
}
if (request.getReturnBinary() != null && !request.getReturnBinary()) {
request.setReturnBinary(null);
}
}
private String getJson(Object request) {
String json;
try {
json = Mappers.writeValueAsString(request);
} catch (IllegalArgumentException e) {
json = "{}";
}
return json;
}
/**
*
* Connects to the refinements service, parses the response into a model
* Retrieves at most 10,000 refinements for the navigation specified.
*
*
* @param query
* A query representing the search.
* @param navigationName
* The name of the navigation to get more refinements for.
*
* @return RefinementsResult object from the refinements service
*
* @throws IOException
*/
public RefinementsResult refinements(Query query, String navigationName) throws IOException {
InputStream data = fireRequest(getBridgeRefinementsUrl(), query.getQueryUrlParams(), query.getBridgeRefinementsJson(clientKey, navigationName), query.isReturnBinary());
return mapRefinements(data, query.isReturnBinary());
}
/**
* @internal
*/
public String getBridgeRefinementsUrl() {
return bridgeRefinementsUrl;
}
protected RefinementsResult mapRefinements(InputStream data, boolean returnBinary) {
return Mappers.readValue(data, RefinementsResult.class, returnBinary);
}
/**
* @internal
* use RefinementsRequest object for refinement searches
*/
public RefinementsResult refinements(RefinementsRequest request) throws IOException {
return refinements(request, Collections.emptyMap());
}
/**
* @internal
* use RefinementsRequest object for refinement searches
*/
public RefinementsResult refinements(RefinementsRequest request, Map headers) throws IOException {
makeBackwardsCompatible(request.getOriginalQuery());
String json = getJson(request);
Boolean returnBinary = request.getOriginalQuery().getReturnBinary() == null ? false : request.getOriginalQuery().getReturnBinary();
InputStream data = fireRequest(getBridgeRefinementsUrl(), request.getOriginalQuery().getQueryUrlParams(), headers, json, returnBinary);
return mapRefinements(data, returnBinary);
}
/**
*
* Cleanup HTTP connection pool.
*
*/
public void shutdown() {
try {
idleConnectionMonitor.shutdown();
httpClient.close();
} catch (IOException e) {
// silently close
}
}
/**
*
* Sets the retry timeout for a failed request.
*
*
* @param retryTimeout the retry timeout
*/
public void setRetryTimeout(long retryTimeout) {
this.retryTimeout = retryTimeout;
}
/**
*
* Sets the maximum number of times to try a request before returning an error.
*
*
* @param maxTries the maximum number of request attempts
*/
public void setMaxTries(long maxTries) {
this.maxTries = maxTries;
}
public List getHeaders() {
return headers;
}
/**
*
* Set a list of headers. Use `getHeaders().add(new BasicHeader())`
*
* @param headers The list of headers
*/
public void setHeaders(List headers) {
this.headers = headers;
}
/**
*
* Generates a secured payload
*
* @param customerId The customerId as seen in Command Center. Ensure this is not the subdomain, which can be `customerId-cors.groupbycloud.com`
* @param clientKey The customerId as seen in Command Center
* @param query The query to encrypt
*/
public static AesContent generateSecuredPayload(String customerId, String clientKey, Query query) throws GeneralSecurityException {
return generateSecuredPayload(customerId, clientKey, query.getBridgeJson(null));
}
/**
*
* Generates a secured payload
*
* @param customerId The customerId as seen in Command Center. Ensure this is not the subdomain, which can be `customerId-cors.groupbycloud.com`
* @param clientKey The customerId as seen in Command Center
* @param requestJson The query to encrypt
*/
public static AesContent generateSecuredPayload(String customerId, String clientKey, String requestJson) throws GeneralSecurityException {
AesEncryption encryption = new AesEncryption(clientKey, customerId);
return encryption.encrypt(requestJson);
}
/**
*
* Generates a secured payload
*
* @param customerId The customerId as seen in Command Center. Ensure this is not the subdomain, which can be `customerId-cors.groupbycloud.com`
* @param query The query to encrypt
*/
public AesContent generateSecuredPayload(String customerId, Query query) throws GeneralSecurityException {
return generateSecuredPayload(customerId, clientKey, query);
}
}