Java tutorial
package com.tune.reporting.base.endpoints; /** * EndpointBase.java * * <p> * Copyright (c) 2015 TUNE, Inc. * All rights reserved. * </p> * * <p> * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * <p> * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * </p> * * <p> * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * </p> * * <p> * Java Version 1.6 * </p> * * <p> * @category tune-reporting * @package com.tune.reporting * @author Jeff Tanner jefft@tune.com * @copyright 2015 TUNE, Inc. (http://www.tune.com) * @license http://opensource.org/licenses/MIT The MIT License (MIT) * @version $Date: 2015-04-16 15:41:32 $ * @link https://developers.mobileapptracking.com @endlink * </p> */ import com.tune.reporting.base.service.TuneServiceClient; import com.tune.reporting.base.service.TuneServiceResponse; import com.tune.reporting.helpers.SdkConfig; import com.tune.reporting.helpers.TuneSdkException; import com.tune.reporting.helpers.TuneServiceException; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.lang.System; /** * Base class for all TUNE Service API endpoints. */ public class EndpointBase { /** * The request has succeeded. */ public static final int HTTP_STATUS_OK = 200; /** Gather all fields for this endpoint. */ public static final int TUNE_FIELDS_ALL = 0; /** Gather default fields for this endpoint. */ public static final int TUNE_FIELDS_DEFAULT = 1; /** Gather related fields for this endpoint. */ public static final int TUNE_FIELDS_RELATED = 2; /** Gather minimal set of fields for this endpoint. */ public static final int TUNE_FIELDS_MINIMAL = 4; /** Gather recommended fields for this endpoint. */ public static final int TUNE_FIELDS_RECOMMENDED = 8; /** * TUNE Reporting SDK Configuration. */ private SdkConfig sdkConfig = null; /** * TUNE Service API Endpoint. */ private String controller = null; /** * TUNE Reporting Authentication Key: * MobileAppTracking API_KEY or Session token. */ private String authKey = null; /** * TUNE Reporting Authentication Type: * api_key OR session_token. */ private String authType = null; /** * TUNE Reporting Authentication Key. * * @return String */ public final String getAuthKey() { return this.authKey; } /** * TUNE Reporting Authentication Type. * * @return String */ public final String getAuthType() { return this.authType; } /** * TUNE Service API Endpoint's fields. */ private Map<String, Map<String, String>> endpointFields = null; /** * Validate action's parameters against this endpoint' fields. */ private Boolean isValidateFields = false; /** * Endpoint's model name. */ private String endpointModelName = null; /** * Parameter access modes. */ public static final Set<String> AUTH_TYPE = new HashSet<String>(Arrays.asList("api_key", "session_token")); /** * Parameter 'sort' directions. */ public static final Set<String> SORT_DIRECTIONS = new HashSet<String>(Arrays.asList("DESC", "ASC")); /** * Parameter 'filter' expression operations. */ protected static final Set<String> FILTER_OPERATIONS = new HashSet<String>(Arrays.asList("=", "!=", "<", "<=", ">", ">=", "IS", "NOT", "NULL", "IN", "LIKE", "RLIKE", "REGEXP", "BETWEEN")); /** * Parameter 'filter' expression conjunctions. */ protected static final Set<String> FILTER_CONJUNCTIONS = new HashSet<String>(Arrays.asList("AND", "OR")); /** * Recommended fields for report exports. */ private Set<String> fieldsRecommended = null; /** * Get recommended fields for an endpoint. * * @return Set */ protected final Set<String> getFieldsRecommended() { return this.fieldsRecommended; } /** * Set recommended fields for an endpoint. * * @param fieldsRecommended Set of recommended fields for this endpoint. */ protected final void setFieldsRecommended(final Set<String> fieldsRecommended) { this.fieldsRecommended = fieldsRecommended; } /** * Parameter 'format' for export report. */ protected static final Set<String> REPORT_EXPORT_FORMATS = new HashSet<String>(Arrays.asList("csv", "json")); /** * Constructor. * * @param controller TUNE Service API Endpoint. */ public EndpointBase(final String controller, final boolean authSdkConfig) throws TuneSdkException { // controller if ((null == controller) || controller.isEmpty()) { throw new IllegalArgumentException("Parameter 'controller' is not defined."); } this.sdkConfig = SdkConfig.getInstance(); try { this.sdkConfig = SdkConfig.getInstance(); } catch (TuneSdkException e) { throw e; } String authKey = null; String authType = null; if (authSdkConfig) { authKey = this.sdkConfig.getAuthKey(); authType = this.sdkConfig.getAuthType(); Boolean isValidateFields = this.sdkConfig.getValidateFields(); // authKey if ((null == authKey) || authKey.isEmpty()) { throw new IllegalArgumentException("Parameter 'authKey' is not defined."); } // authType if ((null == authKey) || authKey.isEmpty()) { throw new IllegalArgumentException("Parameter 'authType' is not defined."); } } this.authKey = authKey; this.authType = authType; this.isValidateFields = isValidateFields; this.controller = controller; } /** * Get controller property for this request. * * @return String */ public final String getController() { return this.controller; } /** * Call TUNE Service API service for this controller. * * @param action TUNE Service API endpoint's action name. * @param strAuthKey API Key or Session Token. * @param strAuthType "api_key" or "session_token". * @param mapQueryString Action's query string parameters. * * @return TuneServiceResponse * @throws TuneSdkException If error within SDK. */ protected final TuneServiceResponse call(final String strAction, final String strAuthKey, final String strAuthType, final Map<String, String> mapQueryString) throws TuneSdkException { // action if ((null == strAction) || strAction.isEmpty()) { throw new IllegalArgumentException("Parameter 'action' is not defined."); } // strAuthKey if ((null == strAuthKey) || strAuthKey.isEmpty()) { throw new IllegalArgumentException("Parameter 'strAuthKey' is not defined."); } // strAuthType if ((null == strAuthType) || strAuthType.isEmpty()) { throw new IllegalArgumentException("Parameter 'strAuthType' is not defined."); } TuneServiceClient client = new TuneServiceClient(this.controller, strAction, strAuthKey, strAuthType, mapQueryString); client.call(); return client.getResponse(); } /** * Call TUNE Service API service for this controller. * * @param strAction TUNE Service API endpoint's action name * @param mapQueryString Action's query string parameters * * @return TuneServiceResponse * @throws TuneSdkException If error within SDK. */ protected final TuneServiceResponse call(final String strAction, final Map<String, String> mapQueryString) throws TuneSdkException { // action if ((null == strAction) || strAction.isEmpty()) { throw new IllegalArgumentException("Parameter 'action' is not defined."); } TuneServiceClient client = new TuneServiceClient(this.controller, strAction, this.getAuthKey(), this.getAuthType(), mapQueryString); client.call(); return client.getResponse(); } /** * Provide complete definition for this endpoint. * * @return TuneServiceResponse * * @throws TuneSdkException If error within SDK. */ public final TuneServiceResponse getDefine() throws TuneSdkException { return this.call("define", null); } /** * Gather all fields available for this endpoint. * * @return String Comma delimited set of all fields available for this * endpoint. * * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final String getFields() throws TuneSdkException, TuneServiceException { return this.getFields(TUNE_FIELDS_ALL); } /** * Gather reqested set of fields for this endpoint. * * @param enumFieldsSelection Which set of fields for this endpoint. * * @return String Comma delimited set of all fields available for this * endpoint. * * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final String getFields(final int enumFieldsSelection) throws TuneSdkException, TuneServiceException { // build fields StringBuilder sb = new StringBuilder(); String loopDelim = ""; Set<String> fields = this.getFieldsSet(enumFieldsSelection); if ((null == fields) || fields.isEmpty()) { return null; } for (String field : fields) { sb.append(loopDelim); sb.append(field.trim()); loopDelim = ","; } return sb.toString(); } /** * Get model name for this endpoint. * * @return String * * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final String getModelName() throws TuneSdkException, TuneServiceException { if (null == this.endpointFields) { this.getFieldsSet(TUNE_FIELDS_ALL); } return this.endpointModelName; } /** * Gather reqested set of fields for this endpoint. * * @return Set All fields available for this endpoint. * * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final Set<String> getFieldsSet() throws TuneSdkException, TuneServiceException { return this.getFieldsSet(TUNE_FIELDS_ALL); } /** * Get all fields for assigned endpoint. * * @param enumFieldsSelection Bitwise selection of requested fields. * * @return Set Array of fields for endpoint. * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final Set<String> getFieldsSet(final int enumFieldsSelection) throws TuneSdkException, TuneServiceException { if ((this.isValidateFields || ((enumFieldsSelection & TUNE_FIELDS_RECOMMENDED) == 0)) && (null == this.endpointFields)) { this.getEndpointFields(); if ((null == this.endpointFields) || this.endpointFields.isEmpty()) { throw new TuneSdkException( String.format("Failed to get fields for endpoint: '%s'.", this.controller)); } } if (enumFieldsSelection == TUNE_FIELDS_ALL) { return this.endpointFields.keySet(); } if ((enumFieldsSelection & TUNE_FIELDS_RECOMMENDED) != 0) { return this.fieldsRecommended; } if (((enumFieldsSelection & TUNE_FIELDS_DEFAULT) == 0) && ((enumFieldsSelection & TUNE_FIELDS_RELATED) != 0)) { return this.endpointFields.keySet(); } Map<String, Map<String, String>> fieldsFiltered = new HashMap<String, Map<String, String>>(); Iterator<Map.Entry<String, Map<String, String>>> it = this.endpointFields.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Map<String, String>> pairs = it.next(); String fieldName = pairs.getKey(); Map<String, String> fieldInfo = pairs.getValue(); if (((enumFieldsSelection & TUNE_FIELDS_RELATED) == 0) && ((enumFieldsSelection & TUNE_FIELDS_MINIMAL) == 0) && (Boolean.parseBoolean(fieldInfo.get("related")))) { continue; } if (((enumFieldsSelection & TUNE_FIELDS_DEFAULT) == 0) && (Boolean.parseBoolean(fieldInfo.get("related")))) { fieldsFiltered.put(fieldName, fieldInfo); } if (((enumFieldsSelection & TUNE_FIELDS_DEFAULT) != 0) && (Boolean.parseBoolean(fieldInfo.get("default")))) { if (((enumFieldsSelection & TUNE_FIELDS_MINIMAL) != 0) && (Boolean.parseBoolean(fieldInfo.get("related")))) { Set<String> relatedFields = new HashSet<String>(); relatedFields.add(".name"); relatedFields.add(".ref"); for (String relatedField : relatedFields) { if (fieldName.endsWith(relatedField)) { fieldsFiltered.put(fieldName, fieldInfo); } } continue; } fieldsFiltered.put(fieldName, fieldInfo); continue; } if (((enumFieldsSelection & TUNE_FIELDS_DEFAULT) != 0) && (Boolean.parseBoolean(fieldInfo.get("related")))) { fieldsFiltered.put(fieldName, fieldInfo); continue; } } return fieldsFiltered.keySet(); } /** * Fetch all fields from model and related models of this endpoint. * * @return Map All endpoint fields. * * @throws TuneServiceException If service fails to handle post request. * @throws TuneSdkException If error within SDK. */ protected final Map<String, Map<String, String>> getEndpointFields() throws TuneServiceException, TuneSdkException { Map<String, String> mapQueryString = new HashMap<String, String>(); mapQueryString.put("controllers", this.controller); mapQueryString.put("details", "modelName,fields"); TuneServiceClient client = new TuneServiceClient("apidoc", "get_controllers", this.getAuthKey(), this.getAuthType(), mapQueryString); client.call(); TuneServiceResponse response = client.getResponse(); int httpCode = response.getHttpCode(); JSONArray data = (JSONArray) response.getData(); if (httpCode != HTTP_STATUS_OK) { String requestUrl = response.getRequestUrl(); throw new TuneServiceException( String.format("Connection failure '%s': Request: '%s'", httpCode, requestUrl)); } if ((null == data) || (data.length() == 0)) { String requestUrl = response.getRequestUrl(); throw new TuneServiceException(String.format("Failed to get fields for endpoint: '%s', Request: '%s'", this.controller, requestUrl)); } try { JSONObject endpointMetaData = data.getJSONObject(0); this.endpointModelName = endpointMetaData.getString("modelName"); JSONArray endpointFields = endpointMetaData.getJSONArray("fields"); Map<String, Map<String, String>> fieldsFound = new HashMap<String, Map<String, String>>(); Map<String, Set<String>> relatedFields = new HashMap<String, Set<String>>(); for (int i = 0; i < endpointFields.length(); i++) { JSONObject endpointField = endpointFields.getJSONObject(i); Boolean fieldRelated = endpointField.getBoolean("related"); String fieldType = endpointField.getString("type"); String fieldName = endpointField.getString("name"); Boolean fieldDefault = endpointField.has("fieldDefault") ? endpointField.getBoolean("fieldDefault") : false; if (fieldRelated) { if (fieldType.equals("property")) { String relatedProperty = fieldName; if (!relatedFields.containsKey(relatedProperty)) { relatedFields.put(relatedProperty, new HashSet<String>()); } continue; } String[] fieldRelatedNameParts = fieldName.split("\\."); String relatedProperty = fieldRelatedNameParts[0]; String relatedFieldName = fieldRelatedNameParts[1]; if (!relatedFields.containsKey(relatedProperty)) { relatedFields.put(relatedProperty, new HashSet<String>()); } Set<String> relatedFieldFields = relatedFields.get(relatedProperty); relatedFieldFields.add(relatedFieldName); relatedFields.put(relatedProperty, relatedFieldFields); continue; } Map<String, String> fieldFoundInfo = new HashMap<String, String>(); fieldFoundInfo.put("default", Boolean.toString(fieldDefault)); fieldFoundInfo.put("related", "false"); fieldsFound.put(fieldName, fieldFoundInfo); } Map<String, Map<String, String>> fieldsFoundMerged = new HashMap<String, Map<String, String>>(); Iterator<Map.Entry<String, Map<String, String>>> it = fieldsFound.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Map<String, String>> pairs = it.next(); String fieldFoundName = pairs.getKey(); Map<String, String> fieldFoundInfo = pairs.getValue(); fieldsFoundMerged.put(fieldFoundName, fieldFoundInfo); if ((fieldFoundName != "_id") && fieldFoundName.endsWith("_id")) { String relatedProperty = fieldFoundName.substring(0, fieldFoundName.length() - 3); if (relatedFields.containsKey(relatedProperty) && !relatedFields.get(relatedProperty).isEmpty()) { for (String relatedFieldName : relatedFields.get(relatedProperty)) { if ("id" == relatedFieldName) { continue; } String relatedPropertyFieldName = String.format("%s.%s", relatedProperty, relatedFieldName); Map<String, String> relatedPropertyFieldInfo = new HashMap<String, String>(); relatedPropertyFieldInfo.put("default", fieldFoundInfo.get("default")); relatedPropertyFieldInfo.put("related", "true"); fieldsFoundMerged.put(relatedPropertyFieldName, relatedPropertyFieldInfo); } } else { Map<String, String> relatedPropertyFieldInfo = new HashMap<String, String>(); relatedPropertyFieldInfo.put("default", fieldFoundInfo.get("default")); relatedPropertyFieldInfo.put("related", "true"); String relatedPropertyFieldName = String.format("%s.%s", relatedProperty, "name"); fieldsFoundMerged.put(relatedPropertyFieldName, relatedPropertyFieldInfo); } } } this.endpointFields = fieldsFoundMerged; } catch (JSONException ex) { throw new TuneSdkException(ex.getMessage(), ex); } catch (Exception ex) { throw new TuneSdkException(ex.getMessage(), ex); } return this.endpointFields; } /** * Validate query string parameter 'fields' having valid endpoint's fields. * * @param fields Set of fields to validate. * * @return String Validated fields. * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public final String doValidateFields(final List<String> fields) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if ((null == fields) || fields.isEmpty()) { throw new IllegalArgumentException("Parameter 'fields' is not defined."); } if (EndpointBase.hasDuplicate(fields)) { throw new IllegalArgumentException("Parameter 'fields' has duplicates."); } if (this.isValidateFields) { if (null == this.endpointFields || this.endpointFields.isEmpty()) { this.getFieldsSet(); } Set<String> endpointFields = this.endpointFields.keySet(); for (String field : fields) { field = field.trim(); if (field.isEmpty()) { throw new TuneSdkException(String.format("Parameter 'fields' contains an empty field.", field)); } if (!endpointFields.contains(field)) { throw new TuneSdkException( String.format("Parameter 'fields' contains an invalid field: '%s'.", field)); } } } return EndpointBase.implode(fields, ","); } /** * Validate parameter 'fields' within string. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ @SuppressWarnings("unchecked") public final Map<String, String> validateFields(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if (!mapParams.containsKey("fields")) { throw new IllegalArgumentException("Key 'fields' is not defined."); } Object value = mapParams.get("fields"); String fields = null; if (value instanceof String) { fields = (String) value; } else if (value instanceof List) { fields = EndpointBase.implode((List<String>) value, ","); } if ((null == fields) || fields.isEmpty()) { throw new IllegalArgumentException("Parameter 'fields' is not defined."); } String[] fieldsArray = fields.split("\\,"); if (fieldsArray.length == 0) { throw new IllegalArgumentException("Parameter 'fields' is not defined."); } List<String> setFields = Arrays.asList(fieldsArray); fields = this.doValidateFields(setFields); mapQueryString.put("fields", fields); return mapQueryString; } /** * Validate query string parameter 'group' having valid endpoint's fields. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ @SuppressWarnings("unchecked") public final Map<String, String> validateGroup(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if (!mapParams.containsKey("group")) { throw new IllegalArgumentException("Key 'group' is not defined."); } Object value = mapParams.get("group"); String group = null; if (value instanceof String) { group = (String) value; } else if (value instanceof List) { group = EndpointBase.implode((List<String>) value, ","); } if ((null == group) || group.isEmpty()) { throw new IllegalArgumentException("Parameter 'group' is not defined."); } String[] groupArray = group.split("\\,"); if (groupArray.length == 0) { throw new IllegalArgumentException("Parameter 'group' is not defined."); } List<String> setGroup = Arrays.asList(groupArray); group = this.doValidateFields(setGroup); mapQueryString.put("group", group); return mapQueryString; } /** * Validate query string parameter 'sort' having valid endpoint's * fields and direction. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ @SuppressWarnings("unchecked") public final Map<String, String> validateSort(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if (!mapParams.containsKey("sort")) { throw new IllegalArgumentException("Key 'sort' is not defined."); } Map<String, String> sort = (HashMap<String, String>) mapParams.get("sort"); if ((null == sort) || sort.isEmpty()) { throw new IllegalArgumentException("Key 'sort' is not defined."); } Set<String> sortFields = sort.keySet(); if (this.isValidateFields) { if (null == this.endpointFields || this.endpointFields.isEmpty()) { this.getFieldsSet(); } Set<String> endpointFields = this.endpointFields.keySet(); for (String sortField : sortFields) { sortField = sortField.trim(); if (sortField.isEmpty()) { throw new IllegalArgumentException( String.format("Parameter 'sort' contains an empty field.", sortField)); } if (!endpointFields.contains(sortField)) { throw new IllegalArgumentException( String.format("Parameter 'sort' contains an invalid field: '%s'.", sortField)); } String sortDirection = sort.get(sortField); sortDirection = sortDirection.toUpperCase(); if (!EndpointBase.SORT_DIRECTIONS.contains(sortDirection)) { throw new IllegalArgumentException( String.format("Parameter 'sort' contains an invalid direction: '%s'.", sortDirection)); } } } StringBuilder sb = new StringBuilder(); for (String sortField : sortFields) { sortField = sortField.trim(); String sortDirection = sort.get(sortField); sortDirection = sortDirection.toUpperCase(); String sortParameter = String.format("%s:%s", sortField, sortDirection); if (sb.length() == 0) { sb.append(sortParameter); } else { sb.append(",").append(sortParameter); } } mapQueryString.put("sort", sb.toString()); return mapQueryString; } /** * Validate query string parameter 'filter' having valid endpoint's fields * and filter expressions. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public final Map<String, String> validateFilter(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if (!mapParams.containsKey("filter")) { throw new IllegalArgumentException("Key 'filter' is not defined."); } String filter = (String) mapParams.get("filter"); if ((null == filter) || filter.isEmpty()) { throw new IllegalArgumentException("Parameter 'filter' is not defined."); } if (!EndpointBase.isParenthesesBalanced(filter)) { throw new IllegalArgumentException( String.format("Invalid parameter 'filter' is not parentheses balanced: '%s'.", filter)); } String filterPruned = filter; filterPruned = filterPruned.replaceAll("\\(", " "); filterPruned = filterPruned.replaceAll("\\)", " "); filterPruned = filterPruned.trim().replaceAll(" +", " "); String[] filterParts = filterPruned.split(" "); Set<String> endpointFields = null; if (this.isValidateFields) { if (null == this.endpointFields || this.endpointFields.isEmpty()) { this.getFieldsSet(); } endpointFields = this.endpointFields.keySet(); } for (String filterPart : filterParts) { filterPart = filterPart.trim(); if (filterPart.isEmpty()) { continue; } if (filterPart.startsWith("'") && filterPart.endsWith("'")) { continue; } try { Integer.parseInt(filterPart); continue; } catch (NumberFormatException e) { // ignore } if (EndpointBase.FILTER_OPERATIONS.contains(filterPart)) { continue; } if (EndpointBase.FILTER_CONJUNCTIONS.contains(filterPart)) { continue; } if (filterPart.matches("[a-z0-9\\.\\_]+")) { if (this.isValidateFields) { if (endpointFields.contains(filterPart)) { continue; } } else { continue; } } throw new IllegalArgumentException( String.format("Parameter 'filter' is invalid: '%s': '%s'.", filter, filterPart)); } mapQueryString.put("filter", String.format("(%s)", filter)); return mapQueryString; } /** * Validates that provided date time is either "yyyy-MM-dd" * or "yyyy-MM-dd HH:mm:ss". * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public Map<String, String> validateDateTime(final Map<String, Object> mapParams, final String paramName, Map<String, String> mapQueryString) throws IllegalArgumentException { if ((null == paramName) || paramName.isEmpty()) { throw new IllegalArgumentException("Parameter 'paramName' is not defined."); } if (!mapParams.containsKey(paramName)) { throw new IllegalArgumentException("Parameter '" + paramName + "' is not defined."); } Object value = mapParams.get(paramName); if (!(value instanceof String)) { throw new IllegalArgumentException("Parameter '" + paramName + "' is not valid type."); } String dateTime = (String) value; dateTime = dateTime.trim(); if ((null == dateTime) || dateTime.isEmpty()) { throw new IllegalArgumentException(String.format("Parameter '%s' is not defined.", paramName)); } SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat simpleDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); boolean validDateTime = false; try { Date date = simpleDateFormat.parse(dateTime); if (dateTime.equals(simpleDateFormat.format(date))) { validDateTime = true; } Date datetime = simpleDateTimeFormat.parse(dateTime); if (dateTime.equals(simpleDateTimeFormat.format(datetime))) { validDateTime = true; } } catch (ParseException ex) { throw new IllegalArgumentException( String.format("Parameter '%s' is invalid date-time: '%s'.", paramName, dateTime), ex); } if (!validDateTime) { throw new IllegalArgumentException( String.format("Parameter '%s' is invalid date-time: '%s'.", paramName, dateTime)); } mapQueryString.put(paramName, dateTime); return mapQueryString; } /** * Validates response timezone. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public final Map<String, String> validateResponseTimezone(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException { if (!mapParams.containsKey("response_timezone")) { throw new IllegalArgumentException("Key 'response_timezone' is not defined."); } String strResponseTimezone = (String) mapParams.get("response_timezone"); strResponseTimezone = strResponseTimezone.trim(); if (strResponseTimezone.isEmpty()) { throw new IllegalArgumentException("Key 'response_timezone' is not defined."); } mapQueryString.put("response_timezone", strResponseTimezone); return mapQueryString; } /** * Validates pagination limit. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public final Map<String, String> validateLimit(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException { if (!mapParams.containsKey("limit")) { throw new IllegalArgumentException("Key 'limit' is not defined."); } Object value = mapParams.get("limit"); if (!(value instanceof Integer)) { throw new IllegalArgumentException("Key 'limit' is not defined."); } Integer limit = (Integer) value; if (0 > limit) { throw new IllegalArgumentException(String.format("Parameter 'limit' is invalid: '%d'.", limit)); } mapQueryString.put("limit", limit.toString()); return mapQueryString; } /** * Validates pagination page. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public final Map<String, String> validatePage(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws TuneSdkException, TuneServiceException { if (!mapParams.containsKey("page")) { throw new IllegalArgumentException("Key 'page' is not defined."); } Object value = mapParams.get("page"); if (!(value instanceof Integer)) { throw new IllegalArgumentException("Key 'page' is not defined."); } Integer page = (Integer) value; if (0 > page) { throw new IllegalArgumentException(String.format("Parameter 'page' is invalid: '%d'.", page)); } mapQueryString.put("page", page.toString()); return mapQueryString; } /** * Validates export format. * * @param mapParams Action parameter mapping * @param mapQueryString Query String parameter mapping * * @return dict * @throws IllegalArgumentException invalid parameter. */ public final Map<String, String> validateFormat(final Map<String, Object> mapParams, Map<String, String> mapQueryString) throws IllegalArgumentException { if (!mapParams.containsKey("format")) { throw new IllegalArgumentException("Key 'format' is not defined."); } String strFormat = (String) mapParams.get("format"); strFormat = strFormat.trim(); if (strFormat.isEmpty()) { throw new IllegalArgumentException("Key 'format' is not defined."); } strFormat = strFormat.toLowerCase(); if (!EndpointBase.REPORT_EXPORT_FORMATS.contains(strFormat)) { throw new IllegalArgumentException(String.format("Parameter 'format' is invalid: '%s'.", strFormat)); } mapQueryString.put("format", strFormat); return mapQueryString; } /** * Helper function for fetching report document given provided job identifier. * * <p> * Requesting for report url is not the same for all report endpoints. * </p> * * @param exportController Controller for report export status. * @param exportAction Action for report export status. * @param jobId Job Identifier of report on queue. * * @return TuneServiceResponse * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ protected final TuneServiceResponse fetchRecords(final String exportController, final String exportAction, final String jobId) throws IllegalArgumentException, TuneServiceException, TuneSdkException { if ((null == exportController) || exportController.isEmpty()) { throw new IllegalArgumentException("Parameter 'exportController' is not defined."); } if ((null == exportAction) || exportAction.isEmpty()) { throw new IllegalArgumentException("Parameter 'exportAction' is not defined."); } if ((null == jobId) || jobId.isEmpty()) { throw new IllegalArgumentException("Parameter 'jobId' is not defined."); } Integer sleep = this.sdkConfig.getFetchSleep(); Integer timeout = this.sdkConfig.getFetchTimeout(); Boolean verbose = this.sdkConfig.getFetchVerbose(); ReportExportWorker exportWorker = new ReportExportWorker(exportController, exportAction, this.authKey, this.authType, jobId, verbose, sleep, timeout); if (verbose) { System.out.println("Starting worker..."); } if (exportWorker.run()) { if (verbose) { System.out.println("Completed worker..."); } } TuneServiceResponse response = exportWorker.getResponse(); if (null == response) { throw new TuneServiceException("Report export request no response."); } int httpCode = response.getHttpCode(); if (httpCode != HTTP_STATUS_OK) { throw new TuneServiceException(String.format("Report export request error: '%d'", httpCode)); } JSONObject jdata = (JSONObject) response.getData(); if (null == jdata) { throw new TuneServiceException("Report export response failed to get data."); } if (!jdata.has("status")) { throw new TuneSdkException(String.format("Export data does not contain report 'status', response: %s", response.toString())); } String status = null; try { status = jdata.getString("status"); } catch (JSONException ex) { throw new TuneSdkException(ex.getMessage(), ex); } catch (Exception ex) { throw new TuneSdkException(ex.getMessage(), ex); } if (status.equals("fail")) { throw new TuneSdkException( String.format("Report export status '%s':, response: %s", status, response.toString())); } return response; } /** * For debug purposes, provide string representation of this object. * * @return String Stringified contents of this object. */ public final String toString() { return String.format("Endpoint '%s'"); } /** * Parse response and gather job identifier. * * @param response @see TuneServiceResponse * * @return String Report Job ID upon Export queue. * @throws TuneServiceException If service fails to handle post request. * @throws TuneSdkException If error within SDK. */ public static String parseResponseReportJobId(final TuneServiceResponse response) throws TuneServiceException, TuneSdkException { String jobId = null; if (null == response) { throw new IllegalArgumentException("Parameter 'response' is not defined."); } Object jdata = response.getData(); if (null == jdata) { throw new TuneServiceException("Report request failed to get export data."); } jobId = jdata.toString(); if ((null == jobId) || jobId.isEmpty()) { throw new TuneSdkException("Parameter 'jobId' is not defined."); } return jobId; } /** * Parse response and gather report url. * * @param response @see TuneServiceResponse * * @return String Report URL download from Export queue. * @throws TuneSdkException If error within SDK. * @throws TuneServiceException If service fails to handle post request. */ public static String parseResponseReportUrl(final TuneServiceResponse response) throws IllegalArgumentException, TuneSdkException, TuneServiceException { if (null == response) { throw new IllegalArgumentException("Parameter 'response' is not defined."); } JSONObject jdata = (JSONObject) response.getData(); if (null == jdata) { throw new TuneServiceException("Report export response failed to get data."); } if (!jdata.has("data")) { throw new TuneSdkException( String.format("Export data does not contain report 'data', response: %s", response.toString())); } JSONObject jdataInternal = null; try { jdataInternal = jdata.getJSONObject("data"); } catch (JSONException ex) { throw new TuneSdkException(ex.getMessage(), ex); } catch (Exception ex) { throw new TuneSdkException(ex.getMessage(), ex); } if (null == jdataInternal) { throw new TuneServiceException(String .format("Export data response does not contain 'data', response: %s", response.toString())); } if (!jdataInternal.has("url")) { throw new TuneSdkException(String.format("Export response 'data' does not contain 'url', response: %s", response.toString())); } String jdataInternalUrl = null; try { jdataInternalUrl = jdataInternal.getString("url"); } catch (JSONException ex) { throw new TuneSdkException(ex.getMessage(), ex); } catch (Exception ex) { throw new TuneSdkException(ex.getMessage(), ex); } if ((null == jdataInternalUrl) || jdataInternalUrl.isEmpty()) { throw new TuneSdkException( String.format("Export response 'url' is not defined, response: %s", response.toString())); } return jdataInternalUrl; } /** * Validate any parentheses within string are balanced. * @param str Contents partitioned with parentheses. * @return Boolean If contents contains balanced parentheses returns true. */ public static boolean isParenthesesBalanced(final String str) { if (str.isEmpty()) { return true; } Stack<Character> stack = new Stack<Character>(); for (int i = 0; i < str.length(); i++) { char current = str.charAt(i); if (current == '{' || current == '(' || current == '[') { stack.push(current); } if (current == '}' || current == ')' || current == ']') { if (stack.isEmpty()) { return false; } char last = stack.peek(); if (current == '}' && last == '{' || current == ')' && last == '(' || current == ']' && last == '[') { stack.pop(); } else { return false; } } } return stack.isEmpty(); } /** * Given a list of strings, determine it contains no duplicates. * @param all An iterable set of strings to valid if it contain duplicates. * @return Boolean If duplicate is found then return true. */ public static boolean hasDuplicate(final Iterable<String> all) { Set<String> set = new HashSet<String>(); // Set#add returns false if the set does not change, which // indicates that a duplicate element has been added. for (String each : all) { if (!set.add(each)) { return true; } } return false; } /** * Implode array into a string separated by delimiters. * @param all An iterable set of strings to combine. * @param glue Delimiter to combine set of strings. * @return String Delimited by provided glue. */ public static String implode(final Iterable<String> all, final String glue) { StringBuilder sb = new StringBuilder(); for (String item : all) { item = item.trim(); if (sb.length() == 0) { sb.append(item); } else { sb.append(glue).append(item); } } return sb.toString(); } /** * Explode string into a set of string items. * @param all Contents delimited. * @param glue Delimiter. * @return Set */ public static Set<String> explode(final String all, final String glue) { String[] items = all.split(glue); Set<String> set = new HashSet<String>(); for (String item : items) { set.add(item.trim()); } return set; } }