MetadataManager.java
/*
* Copyright (c) 2014-present, salesforce.com, inc.
* All rights reserved.
* Redistribution and use of this software in source and binary forms, with or
* without modification, are permitted provided that the following conditions
* are met:
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of salesforce.com, inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission of salesforce.com, inc.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.salesforce.androidsdk.smartsync.manager;
import android.text.TextUtils;
import android.util.Log;
import com.salesforce.androidsdk.accounts.UserAccount;
import com.salesforce.androidsdk.app.SalesforceSDKManager;
import com.salesforce.androidsdk.rest.ApiVersionStrings;
import com.salesforce.androidsdk.rest.RestClient;
import com.salesforce.androidsdk.rest.RestRequest;
import com.salesforce.androidsdk.rest.RestResponse;
import com.salesforce.androidsdk.smartstore.app.SmartStoreSDKManager;
import com.salesforce.androidsdk.smartsync.R;
import com.salesforce.androidsdk.smartsync.manager.CacheManager.CachePolicy;
import com.salesforce.androidsdk.smartsync.model.SalesforceObject;
import com.salesforce.androidsdk.smartsync.model.SalesforceObjectLayoutColumn;
import com.salesforce.androidsdk.smartsync.model.SalesforceObjectType;
import com.salesforce.androidsdk.smartsync.model.SalesforceObjectTypeLayout;
import com.salesforce.androidsdk.smartsync.util.Constants;
import com.salesforce.androidsdk.smartsync.util.SOQLBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* This class contains APIs to fetch Salesforce object metadata, recently used
* objects, and other object related data.
*
* @author bhariharan
*/
public class MetadataManager {
private static final String TAG = "SmartSync: MetadataManager";
private static final int MAX_QUERY_LIMIT = 200;
private static final long DEFAULT_METADATA_REFRESH_INTERVAL = 7 * 24 * 60 * 60 * 1000;
// Cache constants.
private static final String MRU_CACHE_TYPE = "recent_objects";
private static final String METADATA_CACHE_TYPE = "metadata";
private static final String LAYOUT_CACHE_TYPE = "layout";
private static final String SMART_SCOPES_CACHE_KEY = "smart_scopes";
private static final String MRU_BY_OBJECT_TYPE_CACHE_KEY = "mru_for_%s";
private static final String ALL_OBJECTS_CACHE_KEY = "all_objects";
private static final String OBJECT_BY_TYPE_CACHE_KEY = "object_info_%s";
private static final String OBJECT_LAYOUT_BY_TYPE_CACHE_KEY = "object_layout_%s";
// Other constants.
private static final String RECORD_TYPE_GLOBAL = "global";
private static final String RECENTLY_VIEWED = "RecentlyViewed";
private static Map<String, MetadataManager> INSTANCES;
private String apiVersion;
private CacheManager cacheManager;
private RestClient restClient;
private String communityId;
/**
* Returns the instance of this class associated with this user account.
*
* @param account User account.
* @return Instance of this class.
*/
public static synchronized MetadataManager getInstance(UserAccount account) {
return getInstance(account, null);
}
/**
* Returns the instance of this class associated with this user and community.
*
* @param account User account.
* @param communityId Community ID.
* @return Instance of this class.
*/
public static synchronized MetadataManager getInstance(UserAccount account, String communityId) {
if (account == null) {
account = SmartStoreSDKManager.getInstance().getUserAccountManager().getCurrentUser();
}
if (account == null) {
return null;
}
String uniqueId = account.getUserId();
if (UserAccount.INTERNAL_COMMUNITY_ID.equals(communityId)) {
communityId = null;
}
if (!TextUtils.isEmpty(communityId)) {
uniqueId = uniqueId + communityId;
}
MetadataManager instance = null;
if (INSTANCES == null) {
INSTANCES = new HashMap<String, MetadataManager>();
instance = new MetadataManager(account, communityId);
INSTANCES.put(uniqueId, instance);
} else {
instance = INSTANCES.get(uniqueId);
}
if (instance == null) {
instance = new MetadataManager(account, communityId);
INSTANCES.put(uniqueId, instance);
}
return instance;
}
/**
* Resets the metadata manager associated with this user account.
*
* @param account User account.
*/
public static synchronized void reset(UserAccount account) {
reset(account, null);
}
/**
* Resets the metadata manager associated with this user and community.
*
* @param account User account.
* @param communityId Community ID.
*/
public static synchronized void reset(UserAccount account, String communityId) {
if (account == null) {
account = SmartStoreSDKManager.getInstance().getUserAccountManager().getCurrentUser();
}
if (account != null) {
String uniqueId = account.getUserId();
if (UserAccount.INTERNAL_COMMUNITY_ID.equals(communityId)) {
communityId = null;
}
if (!TextUtils.isEmpty(communityId)) {
uniqueId = uniqueId + communityId;
}
if (INSTANCES != null) {
INSTANCES.remove(uniqueId);
}
}
}
/**
* Private parameterized constructor.
*
* @param account User account.
* @param communityId Community ID.
*/
private MetadataManager(UserAccount account, String communityId) {
apiVersion = ApiVersionStrings.getVersionNumber(SalesforceSDKManager.getInstance().getAppContext());
this.communityId = communityId;
cacheManager = CacheManager.getInstance(account, communityId);
restClient = SalesforceSDKManager.getInstance().getClientManager().peekRestClient(account);
}
/**
* Sets the rest client to be used.
* This is primarily used only by tests.
*
* @param restClient
*/
public void setRestClient(RestClient restClient) {
this.restClient = restClient;
}
/**
* Sets the cache manager to be used.
*
* @param cacheMgr CacheManager instance.
*/
public void setCacheManager(CacheManager cacheMgr) {
cacheManager = cacheMgr;
}
/**
* Sets the API version to be used (for example, 'v36.0').
*
* @param apiVer API version to be used.
*/
public void setApiVersion(String apiVer) {
apiVersion = apiVer;
}
/**
* Returns the API version being used.
*
* @return API version being used.
*/
public String getApiVersion() {
return apiVersion;
}
/**
* Returns a list of smart scope object types.
*
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return List of smart scope object types.
*/
public List<SalesforceObjectType> loadSmartScopeObjectTypes(CachePolicy cachePolicy,
long refreshCacheIfOlderThan) {
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
cacheManager.removeCache(MRU_CACHE_TYPE, SMART_SCOPES_CACHE_KEY);
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD) {
cacheManager.removeCache(MRU_CACHE_TYPE, SMART_SCOPES_CACHE_KEY);
}
// Checks the cache for data.
long cachedTime = cacheManager.getLastCacheUpdateTime(MRU_CACHE_TYPE,
SMART_SCOPES_CACHE_KEY);
final List<SalesforceObjectType> cachedData = getCachedObjectTypes(cachePolicy,
MRU_CACHE_TYPE, SMART_SCOPES_CACHE_KEY);
// Returns cache data if the cache policy explicitly states so.
if (cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD) {
return cachedData;
}
if (cachedData != null && cachedData.size() > 0 &&
cachePolicy != CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE &&
!cacheManager.needToReloadCache((cachedData != null), cachePolicy,
cachedTime, refreshCacheIfOlderThan)) {
return cachedData;
}
// Loads data from the server.
return loadSmartScopes(cachePolicy);
}
/**
* Returns a list of MRU objects based on object type.
*
* @param objectTypeName Object type name (set to 'null' for global MRU).
* @param limit Limit on number of objects (max is 'MAX_QUERY_LIMIT').
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @param networkFieldName Network field name for this object type.
* @return List of recently accessed objects.
*/
public List<SalesforceObject> loadMRUObjects(String objectTypeName,
int limit, CachePolicy cachePolicy, long refreshCacheIfOlderThan,
String networkFieldName) {
if (limit > MAX_QUERY_LIMIT || limit < 0) {
limit = MAX_QUERY_LIMIT;
}
String cacheKey;
boolean globalMRU = false;
if (objectTypeName == null || Constants.EMPTY_STRING.equals(objectTypeName)) {
globalMRU = true;
cacheKey = String.format(MRU_BY_OBJECT_TYPE_CACHE_KEY, RECORD_TYPE_GLOBAL);
} else {
cacheKey = String.format(MRU_BY_OBJECT_TYPE_CACHE_KEY, objectTypeName);
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
cacheManager.removeCache(MRU_CACHE_TYPE, cacheKey);
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD) {
cacheManager.removeCache(MRU_CACHE_TYPE, cacheKey);
}
// Checks the cache for data.
long cachedTime = cacheManager.getLastCacheUpdateTime(MRU_CACHE_TYPE, cacheKey);
List<SalesforceObject> cachedData = getCachedObjects(cachePolicy,
MRU_CACHE_TYPE, cacheKey);
// Returns cache data if the cache policy explicitly states so.
if (cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD) {
if (cachedData != null && limit > 0 && limit < cachedData.size()) {
cachedData = cachedData.subList(0, limit - 1);
}
return cachedData;
}
if (cachedData != null && cachedData.size() > 0 &&
cachePolicy != CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE &&
!cacheManager.needToReloadCache((cachedData != null), cachePolicy,
cachedTime, refreshCacheIfOlderThan)) {
if (limit > 0 && limit < cachedData.size()) {
cachedData = cachedData.subList(0, limit - 1);
}
return cachedData;
}
return loadRecentObjects(objectTypeName, globalMRU, limit,
cachePolicy, cacheKey, networkFieldName);
}
/**
* Returns a list of all object types.
*
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return List of all object types.
*/
public List<SalesforceObjectType> loadAllObjectTypes(CachePolicy cachePolicy,
long refreshCacheIfOlderThan) {
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
cacheManager.removeCache(METADATA_CACHE_TYPE, ALL_OBJECTS_CACHE_KEY);
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD) {
cacheManager.removeCache(METADATA_CACHE_TYPE, ALL_OBJECTS_CACHE_KEY);
}
long cachedTime = cacheManager.getLastCacheUpdateTime(METADATA_CACHE_TYPE,
ALL_OBJECTS_CACHE_KEY);
// Checks if the cache needs to be refreshed.
final List<SalesforceObjectType> cachedData = getCachedObjectTypes(cachePolicy,
METADATA_CACHE_TYPE, ALL_OBJECTS_CACHE_KEY);
// Returns cache data if the cache policy explicitly states so.
if (cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD) {
return cachedData;
}
if (cachedData != null && cachedData.size() > 0 &&
cachePolicy != CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE &&
!cacheManager.needToReloadCache((cachedData != null), cachePolicy,
cachedTime, refreshCacheIfOlderThan)) {
return cachedData;
}
// Makes a live server call to fetch object types.
final List<SalesforceObjectType> returnList = new ArrayList<SalesforceObjectType>();
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForDescribeGlobal(apiVersion));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
if (response != null && response.isSuccess()) {
try {
final JSONObject responseJSON = response.asJSONObject();
if (responseJSON != null) {
final JSONArray objectTypes = responseJSON.optJSONArray("sobjects");
if (objectTypes != null) {
for (int i = 0; i < objectTypes.length(); i++) {
final JSONObject metadata = objectTypes.optJSONObject(i);
if (metadata != null) {
final boolean hidden = metadata.optBoolean(
Constants.HIDDEN_FIELD, false);
if (!hidden) {
final SalesforceObjectType objType = new SalesforceObjectType(metadata);
returnList.add(objType);
}
}
}
if (returnList.size() > 0 && shouldCacheData(cachePolicy)) {
cacheObjectTypes(returnList, METADATA_CACHE_TYPE, ALL_OBJECTS_CACHE_KEY);
}
}
}
} catch (IOException e) {
Log.e(TAG, "IOException occurred while reading data", e);
} catch (JSONException e) {
Log.e(TAG, "JSONException occurred while parsing", e);
}
} else if (shouldFallBackOnCache(cachePolicy)) {
return cachedData;
}
if (returnList.size() == 0) {
return null;
}
return returnList;
}
/**
* Returns metadata for a specific object type.
*
* @param objectTypeName Object type name.
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return Metadata for a specific object type.
*/
public SalesforceObjectType loadObjectType(String objectTypeName,
CachePolicy cachePolicy, long refreshCacheIfOlderThan) {
if (objectTypeName == null || Constants.EMPTY_STRING.equals(objectTypeName)) {
Log.e(TAG, "Cannot load recently accessed objects for invalid object type");
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
cacheManager.removeCache(METADATA_CACHE_TYPE,
String.format(OBJECT_BY_TYPE_CACHE_KEY, objectTypeName));
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD) {
cacheManager.removeCache(METADATA_CACHE_TYPE,
String.format(OBJECT_BY_TYPE_CACHE_KEY, objectTypeName));
}
long cachedTime = cacheManager.getLastCacheUpdateTime(METADATA_CACHE_TYPE,
String.format(OBJECT_BY_TYPE_CACHE_KEY, objectTypeName));
// Checks if the cache needs to be refreshed.
final SalesforceObjectType cachedData = getCachedObjectType(objectTypeName);
// Returns cache data if the cache policy explicitly states so.
if (cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD) {
return cachedData;
}
if (cachedData != null && cachePolicy != CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE &&
!cacheManager.needToReloadCache((cachedData != null), cachePolicy,
cachedTime, refreshCacheIfOlderThan)) {
return cachedData;
}
// Makes a live server call to fetch metadata.
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForDescribe(apiVersion, objectTypeName));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
if (response != null && response.isSuccess()) {
try {
final JSONObject responseJSON = response.asJSONObject();
if (responseJSON != null) {
final SalesforceObjectType objType = new SalesforceObjectType(responseJSON);
if (shouldCacheData(cachePolicy)) {
final List<SalesforceObjectType> objList = new ArrayList<SalesforceObjectType>();
objList.add(objType);
cacheObjectTypes(objList, METADATA_CACHE_TYPE,
String.format(OBJECT_BY_TYPE_CACHE_KEY, objectTypeName));
}
return objType;
}
} catch (IOException e) {
Log.e(TAG, "IOException occurred while reading data", e);
} catch (JSONException e) {
Log.e(TAG, "JSONException occurred while parsing", e);
}
} else if (shouldFallBackOnCache(cachePolicy)) {
return cachedData;
}
return null;
}
/**
* Returns metadata for the specified list of object types.
*
* @param objectTypeNames List of object type names.
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return Metadata for the list of object types.
*/
public List<SalesforceObjectType> loadObjectTypes(List<String> objectTypeNames,
CachePolicy cachePolicy, long refreshCacheIfOlderThan) {
if (objectTypeNames == null || objectTypeNames.size() == 0) {
return null;
}
List<SalesforceObjectType> results = new ArrayList<SalesforceObjectType>();
for (final String objectTypeName : objectTypeNames) {
if (objectTypeName != null && !Constants.EMPTY_STRING.equals(objectTypeName)) {
final SalesforceObjectType object = loadObjectType(objectTypeName,
cachePolicy, refreshCacheIfOlderThan);
if (object != null) {
results.add(object);
}
}
}
if (results.size() == 0) {
results = null;
}
return results;
}
/**
* Returns whether the specified object type is searchable or not.
*
* @param objectType Object type.
* @return True - if searchable, False - otherwise.
*/
public boolean isObjectTypeSearchable(SalesforceObjectType objectType) {
if (objectType == null) {
return false;
}
final String objectName = ((objectType.getName() == null) ?
Constants.EMPTY_STRING : objectType.getName());
if (!Constants.EMPTY_STRING.equals(objectName)) {
if (objectType.getRawData() == null) {
objectType = loadObjectType(objectName,
CachePolicy.RELOAD_AND_RETURN_CACHE_DATA, 0);
}
if (objectType != null) {
return (objectType.isSearchable());
}
}
return false;
}
/**
* Returns object type layouts for the specified list of object types.
*
* @param objectTypes List of object types.
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return Object type layouts for the list of object types.
*/
public List<SalesforceObjectTypeLayout> loadObjectTypesLayout(List<SalesforceObjectType> objectTypes,
CachePolicy cachePolicy, long refreshCacheIfOlderThan) {
if (objectTypes == null || objectTypes.size() == 0) {
return null;
}
List<SalesforceObjectTypeLayout> results = new ArrayList<SalesforceObjectTypeLayout>();
for (final SalesforceObjectType objectType : objectTypes) {
if (objectType != null) {
final SalesforceObjectTypeLayout layout = loadObjectTypeLayout(objectType,
cachePolicy, refreshCacheIfOlderThan);
if (layout != null) {
results.add(layout);
}
}
}
if (results.size() == 0) {
results = null;
}
return results;
}
/**
* Loads the object layout for the specified object type.
*
* @param objectType Object type.
* @param cachePolicy Cache policy.
* @param refreshCacheIfOlderThan Time interval to refresh cache.
* @return Object layout.
*/
public SalesforceObjectTypeLayout loadObjectTypeLayout(SalesforceObjectType objectType,
CachePolicy cachePolicy, long refreshCacheIfOlderThan) {
if (objectType == null) {
Log.e(TAG, "Cannot load object layout with an invalid object type");
return null;
}
final String objectTypeName = objectType.getName();
if (objectTypeName == null || Constants.EMPTY_STRING.equals(objectTypeName)) {
Log.e(TAG, "Cannot load object layout with an invalid object type");
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
cacheManager.removeCache(LAYOUT_CACHE_TYPE,
String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY, objectTypeName));
return null;
}
if (cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD) {
cacheManager.removeCache(LAYOUT_CACHE_TYPE,
String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY, objectTypeName));
}
long cachedTime = cacheManager.getLastCacheUpdateTime(LAYOUT_CACHE_TYPE,
String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY, objectTypeName));
// Checks if the cache needs to be refreshed.
final List<SalesforceObjectTypeLayout> cachedData = getCachedObjectLayouts(CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD,
LAYOUT_CACHE_TYPE, String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY, objectTypeName));
// Returns cache data if the cache policy explicitly states so.
if (cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD) {
if (cachedData != null && cachedData.size() > 0) {
return cachedData.get(0);
} else {
return null;
}
}
if (cachedData != null && cachedData.size() > 0 &&
cachePolicy != CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE &&
!cacheManager.needToReloadCache((cachedData != null), cachePolicy,
cachedTime, refreshCacheIfOlderThan)) {
return cachedData.get(0);
}
// Checks if a layout can be loaded for this object type.
if (objectType.getRawData() == null) {
objectType = loadObjectType(objectTypeName, CachePolicy.RELOAD_AND_RETURN_CACHE_DATA, 0);
}
if (objectType == null || !objectType.isSearchable() || !objectType.isLayoutable()) {
return null;
}
// Makes a live server call to fetch the object layout.
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForSearchResultLayout(apiVersion, Arrays.asList(new String[] {objectTypeName})));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
if (response != null && response.isSuccess()) {
try {
final JSONArray responseJSON = response.asJSONArray();
if (responseJSON != null && responseJSON.length() > 0) {
final JSONObject objJSON = responseJSON.optJSONObject(0);
if (objJSON != null) {
final SalesforceObjectTypeLayout objTypeLayout = new SalesforceObjectTypeLayout(objectTypeName,
objJSON);
if (shouldCacheData(cachePolicy)) {
final List<SalesforceObjectTypeLayout> layoutList = new ArrayList<SalesforceObjectTypeLayout>();
layoutList.add(objTypeLayout);
cacheObjectLayouts(layoutList, LAYOUT_CACHE_TYPE,
String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY,
objectTypeName));
}
return objTypeLayout;
}
}
} catch (IOException e) {
Log.e(TAG, "IOException occurred while reading data", e);
} catch (JSONException e) {
Log.e(TAG, "JSONException occurred while parsing", e);
}
} else if (shouldFallBackOnCache(cachePolicy)) {
if (cachedData != null && cachedData.size() > 0) {
return cachedData.get(0);
}
}
return null;
}
/**
* Returns the color resource associated with an object type.
*
* @param objTypeName Object type name.
* @return Color resource associated with the object type.
*/
public int getColorResourceForObjectType(String objTypeName) {
int color = R.color.record_other;
if (objTypeName == null) {
return color;
}
if (Constants.ACCOUNT.equals(objTypeName)) {
color = R.color.record_account;
} else if (Constants.CONTACT.equals(objTypeName)) {
color = R.color.record_contact;
} else if (Constants.TASK.equals(objTypeName)) {
color = R.color.record_task;
} else if (Constants.CASE.equals(objTypeName)) {
color = R.color.record_case;
} else if (Constants.OPPORTUNITY.equals(objTypeName)) {
color = R.color.record_opportunity;
} else if (Constants.LEAD.equals(objTypeName)) {
color = R.color.record_lead;
} else if (Constants.CAMPAIGN.equals(objTypeName)) {
color = R.color.record_campaign;
}
return color;
}
/**
* Returns the community ID if set, returns null otherwise.
*
* @return Community ID, or null if not set.
*/
public String getCommunityId() {
return communityId;
}
/**
* Sets the community ID. All subsequent calls will query
* only within the given network.
*
* @param communityId ID to use for network aware calls.
*/
public void setCommunityId(String communityId) {
this.communityId = communityId;
}
/**
* Marks an object as viewed on the server.
*
* @param objectId Object ID.
* @param objectType Object type.
* @param networkFieldName Network field name for this object type.
*/
public void markObjectAsViewed(String objectId, String objectType,
String networkFieldName) {
if (objectId == null || objectType == null
|| Constants.EMPTY_STRING.equals(objectId)
|| Constants.EMPTY_STRING.equals(objectType)
|| Constants.CONTENT_VERSION.equals(objectType)
|| Constants.CONTENT.equals(objectType)) {
Log.w(TAG, "Cannot mark object as viewed");
return;
}
final SalesforceObjectType result = loadObjectType(objectType,
CachePolicy.RELOAD_IF_EXPIRED_AND_RETURN_CACHE_DATA,
DEFAULT_METADATA_REFRESH_INTERVAL);
final SOQLBuilder queryBuilder = SOQLBuilder.getInstanceWithFields(Constants.ID).from(objectType);
try {
String whereClause;
if (result != null && isObjectTypeSearchable(result)) {
whereClause = String.format("Id = '%s' FOR VIEW", objectId);
} else {
whereClause = String.format("Id = '%s'", objectId);
}
if (communityId != null && networkFieldName != null) {
whereClause = String.format("%s AND %s = '%s'", whereClause,
networkFieldName, communityId);
}
queryBuilder.where(whereClause);
final String queryString = queryBuilder.build();
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForQuery(apiVersion, queryString));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
if (response != null && response.isSuccess()) {
final JSONObject responseJSON = response.asJSONObject();
if (responseJSON != null) {
final JSONArray records = responseJSON.optJSONArray("records");
if (records == null || records.length() == 0) {
Log.e(TAG, "Failed to mark object " + objectId + " as viewed, since object no longer exists");
}
}
}
} catch (JSONException e) {
Log.e(TAG, "Error occurred while attempting to mark object " + objectId + " as viewed", e);
} catch (IOException e) {
Log.e(TAG, "Error occurred while attempting to mark object " + objectId + " as viewed", e);
}
}
/**
* Returns whether a layout can be loaded for the specified object type.
*
* @param objType Object type.
* @return True - if layout can be loaded, False - otherwise.
*/
private boolean canLoadLayoutForObjectType(SalesforceObjectType objType) {
if (objType == null) {
return false;
}
return (objType.isLayoutable() && objType.isSearchable());
}
/**
* Returns a list of layout fields for the specified object type.
*
* @param type Object type.
* @return List of return fields.
*/
private List<String> getLayoutFieldsForObjectType(SalesforceObjectType type) {
if (type == null) {
return null;
}
final SalesforceObjectTypeLayout layout = getCachedObjectLayout(type);
if (layout == null) {
return null;
}
final List<SalesforceObjectLayoutColumn> columns = layout.getColumns();
if (columns == null || columns.size() == 0) {
return null;
}
final List<String> results = new ArrayList<String>();
for (final SalesforceObjectLayoutColumn col : columns) {
if (col != null) {
final String name = col.getName();
if (name != null && !Constants.EMPTY_STRING.equals(name)) {
results.add(name);
}
}
}
if (results.size() == 0) {
return null;
}
return results;
}
/**
* Returns whether the data should be cached.
*
* @param cachePolicy Cache policy.
* @return True - if the data should be cached, False - otherwise.
*/
private boolean shouldCacheData(CachePolicy cachePolicy) {
return ((cachePolicy != CachePolicy.IGNORE_CACHE_DATA)
&& (cachePolicy != CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD)
&& (cachePolicy != CachePolicy.INVALIDATE_CACHE_DONT_RELOAD));
}
/**
* Caches a list of objects.
*
* @param objects List of objects.
* @param cacheType Cache type.
* @param cacheKey Cache key.
*/
private void cacheObjects(List<SalesforceObject> objects,
String cacheType, String cacheKey) {
if (objects != null && objects.size() > 0 &&
cacheType != null && cacheKey != null) {
cacheManager.writeObjects(objects, cacheKey, cacheType);
}
}
/**
* Caches a list of object types.
*
* @param objectTypes List of object types.
* @param cacheType Cache type.
* @param cacheKey Cache key.
*/
private void cacheObjectTypes(List<SalesforceObjectType> objectTypes,
String cacheType, String cacheKey) {
if (objectTypes != null && objectTypes.size() > 0 &&
cacheType != null && cacheKey != null) {
cacheManager.writeObjectTypes(objectTypes, cacheKey, cacheType);
}
}
/**
* Caches a list of object layouts.
*
* @param objects List of object layouts.
* @param cacheType Cache type.
* @param cacheKey Cache key.
*/
private void cacheObjectLayouts(List<SalesforceObjectTypeLayout> objects,
String cacheType, String cacheKey) {
if (objects != null && objects.size() > 0 &&
cacheType != null && cacheKey != null) {
cacheManager.writeObjectLayouts(objects, cacheKey, cacheType);
}
}
/**
* Returns a list of cached objects.
*
* @param cachePolicy Cache policy.
* @param cacheType Cache type.
* @param cacheKey Cache key.
* @return List of cached objects.
*/
private List<SalesforceObject> getCachedObjects(CachePolicy cachePolicy,
String cacheType, String cacheKey) {
if (cachePolicy == CachePolicy.IGNORE_CACHE_DATA ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
return null;
}
return cacheManager.readObjects(cacheType, cacheKey);
}
/**
* Returns cached metadata for a specific object type.
*
* @param objectTypeName Object type name.
* @return Cached metadata for object type.
*/
private SalesforceObjectType getCachedObjectType(String objectTypeName) {
if (objectTypeName == null || Constants.EMPTY_STRING.equals(objectTypeName)) {
return null;
}
SalesforceObjectType result = null;
final List<SalesforceObjectType> objectTypes = getCachedObjectTypes(CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD,
METADATA_CACHE_TYPE, String.format(OBJECT_BY_TYPE_CACHE_KEY, objectTypeName));
if (objectTypes != null && objectTypes.size() > 0) {
result = objectTypes.get(0);
}
return result;
}
/**
* Returns a list of cached object types.
*
* @param cachePolicy Cache policy.
* @param cacheType Cache type.
* @param cacheKey Cache key.
* @return List of cached object types.
*/
private List<SalesforceObjectType> getCachedObjectTypes(CachePolicy cachePolicy,
String cacheType, String cacheKey) {
if (cachePolicy == CachePolicy.IGNORE_CACHE_DATA ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
return null;
}
return cacheManager.readObjectTypes(cacheType, cacheKey);
}
/**
* Returns cached object layout for a specific object type.
*
* @param objectType Object type.
* @return Cached object layout.
*/
private SalesforceObjectTypeLayout getCachedObjectLayout(SalesforceObjectType objectType) {
if (objectType == null) {
return null;
}
final String objectTypeName = objectType.getName();
if (objectTypeName == null || Constants.EMPTY_STRING.equals(objectTypeName)) {
return null;
}
SalesforceObjectTypeLayout result = null;
final List<SalesforceObjectTypeLayout> objectTypes = getCachedObjectLayouts(CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD,
LAYOUT_CACHE_TYPE, String.format(OBJECT_LAYOUT_BY_TYPE_CACHE_KEY, objectTypeName));
if (objectTypes != null && objectTypes.size() > 0) {
result = objectTypes.get(0);
}
return result;
}
/**
* Returns a list of cached object layouts.
*
* @param cachePolicy Cache policy.
* @param cacheType Cache type.
* @param cacheKey Cache key.
* @return List of cached object layouts.
*/
private List<SalesforceObjectTypeLayout> getCachedObjectLayouts(CachePolicy cachePolicy,
String cacheType, String cacheKey) {
if (cachePolicy == CachePolicy.IGNORE_CACHE_DATA ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_AND_RELOAD ||
cachePolicy == CachePolicy.INVALIDATE_CACHE_DONT_RELOAD) {
return null;
}
return cacheManager.readObjectLayouts(cacheType, cacheKey);
}
/**
* Returns fields for the specified object type.
*
* @param objectTypeName Object type name.
* @return Fields for the object type.
*/
private String getReturnFieldsForObjectType(String objectTypeName) {
if (objectTypeName == null) {
return null;
}
final SalesforceObjectType objectType = getCachedObjectType(objectTypeName);
final List<String> returnFields = new ArrayList<String>();
final List<String> extraValues = getLayoutReturnFieldsForObjectType(objectTypeName);
if (extraValues != null && extraValues.size() > 0) {
for (final String extraValue : extraValues) {
if (extraValue != null && !Constants.EMPTY_STRING.equals(extraValue)) {
returnFields.add(extraValue);
}
}
}
if (!returnFields.contains(Constants.ID)) {
returnFields.add(Constants.ID);
}
if (objectType != null) {
final String nameField = objectType.getNameField();
if (nameField != null && !returnFields.contains(nameField)) {
returnFields.add(nameField);
}
}
final StringBuilder result = new StringBuilder();
result.append(returnFields.get(0));
for (int i = 1; i < returnFields.size(); i++) {
final String resultField = returnFields.get(i);
if (resultField != null && !Constants.EMPTY_STRING.equals(resultField)) {
result.append(",");
result.append(resultField);
}
}
if (Constants.EMPTY_STRING.equals(result.toString())) {
return null;
}
return result.toString();
}
/**
* Loads smart scopes using a REST call.
*
* @param cachePolicy Cache policy.
* @return List of object types.
*/
private List<SalesforceObjectType> loadSmartScopes(CachePolicy cachePolicy) {
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForSearchScopeAndOrder(apiVersion));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
List<SalesforceObjectType> recentItems = new ArrayList<SalesforceObjectType>();
if (response != null && response.isSuccess()) {
try {
final JSONArray responseJSON = response.asJSONArray();
if (responseJSON != null) {
for (int i = 0; i < responseJSON.length(); i++) {
final JSONObject object = responseJSON.optJSONObject(i);
if (object != null) {
final String name = object.optString(Constants.TYPE.toLowerCase(Locale.US));
if (name != null && !Constants.EMPTY_STRING.equals(name)) {
final SalesforceObjectType sfObj = new SalesforceObjectType(name);
if (isObjectTypeSearchable(sfObj)) {
recentItems.add(sfObj);
}
}
}
}
}
} catch (IOException e) {
Log.e(TAG, "IOException occurred while reading data", e);
} catch (JSONException e) {
Log.e(TAG, "JSONException occurred while parsing", e);
}
} else if (shouldFallBackOnCache(cachePolicy)) {
recentItems = getCachedObjectTypes(cachePolicy, MRU_CACHE_TYPE,
SMART_SCOPES_CACHE_KEY);
}
if (shouldCacheData(cachePolicy) && recentItems != null
&& recentItems.size() > 0) {
cacheObjectTypes(recentItems, MRU_CACHE_TYPE,
SMART_SCOPES_CACHE_KEY);
}
return recentItems;
}
/**
* Returns recently viewed objects.
*
* @param objectTypeName Object type name.
* @param globalMRU True - if global MRU, False - otherwise.
* @param limit Limit on number of items.
* @param cachePolicy Cache policy.
* @param cacheKey Cache key.
* @param networkFieldName Network field name for this object type.
* @return List of recently viewed objects.
*/
private List<SalesforceObject> loadRecentObjects(String objectTypeName,
boolean globalMRU, int limit, CachePolicy cachePolicy, String cacheKey,
String networkFieldName) {
final List<SalesforceObject> recentItems = new ArrayList<SalesforceObject>();
SOQLBuilder queryBuilder;
if (globalMRU) {
queryBuilder = SOQLBuilder.getInstanceWithFields("Id, Name, Type");
queryBuilder.from(RECENTLY_VIEWED);
String whereClause = "LastViewedDate != NULL";
if (communityId != null) {
whereClause = String.format("%s AND NetworkId = '%s'",
whereClause, communityId);
}
queryBuilder.where(whereClause);
queryBuilder.limit(limit);
} else {
boolean objContainsLastViewedDate = false;
final SalesforceObjectType objType = loadObjectType(objectTypeName,
CachePolicy.RELOAD_IF_EXPIRED_AND_RETURN_CACHE_DATA,
DEFAULT_METADATA_REFRESH_INTERVAL);
if (objType != null) {
final JSONArray fields = objType.getFields();
if (fields != null) {
for (int i = 0; i < fields.length(); i++) {
final JSONObject obj = fields.optJSONObject(i);
if (obj != null) {
final String nameField = obj.optString(Constants.NAME.toLowerCase(Locale.US));
if (nameField != null && "LastViewedDate".equals(nameField)) {
objContainsLastViewedDate = true;
}
}
}
}
}
final String retFields = getReturnFieldsForObjectType(objectTypeName);
if (retFields != null && !Constants.EMPTY_STRING.equals(retFields)) {
queryBuilder = SOQLBuilder.getInstanceWithFields(retFields);
} else {
queryBuilder = SOQLBuilder.getInstanceWithFields("Id, Name, Type");
}
String whereClause;
if (objContainsLastViewedDate) {
queryBuilder.from(String.format("%s using SCOPE MRU", objectTypeName));
whereClause = "LastViewedDate != NULL";
queryBuilder.orderBy("LastViewedDate DESC");
queryBuilder.limit(limit);
} else {
queryBuilder.from(RECENTLY_VIEWED);
whereClause = String.format("LastViewedDate != NULL and Type = '%s'", objectTypeName);
queryBuilder.limit(limit);
}
if (communityId != null) {
if (networkFieldName != null) {
whereClause = String.format("%s AND %s = '%s'",
whereClause, networkFieldName, communityId);
}
}
queryBuilder.where(whereClause);
}
final String query = queryBuilder.build();
RestResponse response = null;
try {
response = restClient.sendSync(RestRequest.getRequestForQuery(apiVersion, query));
} catch(IOException e) {
Log.e(TAG, "IOException occurred while sending request", e);
}
if (response != null && response.isSuccess()) {
try {
final JSONObject responseJSON = response.asJSONObject();
if (responseJSON != null) {
final JSONArray records = responseJSON.optJSONArray("records");
if (records != null) {
for (int i = 0; i < records.length(); i++) {
final JSONObject rec = records.optJSONObject(i);
if (rec != null) {
final SalesforceObject sfObj = new SalesforceObject(rec);
if (globalMRU) {
if (sfObj != null && sfObj.getObjectType() != null
&& sfObj.getObjectType().equals(Constants.CONTENT)) {
sfObj.setObjectType(Constants.CONTENT_VERSION);
}
} else {
String sfObjName = null;
if (sfObj != null) {
sfObj.setObjectType(objectTypeName);
sfObjName = sfObj.getName();
}
if (sfObjName == null || Constants.EMPTY_STRING.equals(sfObjName)
|| Constants.NULL_STRING.equals(sfObjName)) {
final SalesforceObjectType objType = getCachedObjectType(objectTypeName);
if (objType != null) {
final String nameField = objType.getNameField();
if (nameField != null && !Constants.EMPTY_STRING.equals(nameField)) {
final String scopedName = rec.optString(nameField);
if (sfObj != null && scopedName != null) {
sfObj.setName(scopedName);
}
}
}
}
}
recentItems.add(sfObj);
}
}
}
}
} catch (IOException e) {
Log.e(TAG, "IOException occurred while reading data", e);
} catch (JSONException e) {
Log.e(TAG, "JSONException occurred while parsing", e);
}
if (recentItems.size() > 0) {
if (shouldCacheData(cachePolicy)) {
cacheObjects(recentItems, MRU_CACHE_TYPE, cacheKey);
}
return recentItems;
}
} else if (shouldFallBackOnCache(cachePolicy)) {
return getCachedObjects(cachePolicy, MRU_CACHE_TYPE, cacheKey);
}
return null;
}
/**
* Returns the return fields for the object type specified
* in the object layout, if a layout exists for this object type.
*
* @param objTypeName Object type name.
* @return Layout return fields for the object type.
*/
private List<String> getLayoutReturnFieldsForObjectType(String objTypeName) {
if (objTypeName == null || Constants.EMPTY_STRING.equals(objTypeName)) {
return null;
}
List<String> results = null;
SalesforceObjectType objType = getCachedObjectType(objTypeName);
if (objType == null) {
objType = loadObjectType(objTypeName,
CachePolicy.RELOAD_IF_EXPIRED_AND_RETURN_CACHE_DATA,
DEFAULT_METADATA_REFRESH_INTERVAL);
}
if (objType != null && canLoadLayoutForObjectType(objType)) {
results = getLayoutFieldsForObjectType(objType);
}
return results;
}
/**
* Returns whether a method should fall back on cached data or return the
* empty data set from the server, in the event that a server error occurs
* or we do not receive a response from the server, for reasons such as loss
* of connectivity, for instance.
*
* @param cachePolicy Cache policy.
* @return True - if we should fall back on cached data, False - otherwise.
*/
private boolean shouldFallBackOnCache(CachePolicy cachePolicy) {
return (cachePolicy == CachePolicy.RELOAD_AND_RETURN_CACHE_DATA
|| cachePolicy == CachePolicy.RELOAD_AND_RETURN_CACHE_ON_FAILURE
|| cachePolicy == CachePolicy.RETURN_CACHE_DATA_DONT_RELOAD
|| cachePolicy == CachePolicy.RELOAD_IF_EXPIRED_AND_RETURN_CACHE_DATA);
}
}