com.hybris.mobile.lib.commerce.sync.CatalogSyncAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.hybris.mobile.lib.commerce.sync.CatalogSyncAdapter.java

Source

/**
 * ****************************************************************************
 * [y] hybris Platform
 * <p/>
 * Copyright (c) 2000-2015 hybris AG
 * All rights reserved.
 * <p/>
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 * ****************************************************************************
 */

package com.hybris.mobile.lib.commerce.sync;

import android.accounts.Account;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import com.hybris.mobile.lib.commerce.Constants;
import com.hybris.mobile.lib.commerce.R;
import com.hybris.mobile.lib.commerce.data.catalog.CategoryHierarchy;
import com.hybris.mobile.lib.commerce.data.error.ErrorList;
import com.hybris.mobile.lib.commerce.data.product.Product;
import com.hybris.mobile.lib.commerce.data.product.VariantOption;
import com.hybris.mobile.lib.commerce.provider.CatalogContract;
import com.hybris.mobile.lib.commerce.provider.CatalogContract.SyncStatus;
import com.hybris.mobile.lib.commerce.query.QueryCatalog;
import com.hybris.mobile.lib.commerce.query.QueryProduct;
import com.hybris.mobile.lib.commerce.query.QueryProducts;
import com.hybris.mobile.lib.commerce.response.ResponseReceiver;
import com.hybris.mobile.lib.commerce.service.ContentServiceHelper;
import com.hybris.mobile.lib.commerce.utils.ErrorUtils;
import com.hybris.mobile.lib.http.converter.exception.DataConverterException;
import com.hybris.mobile.lib.http.listener.OnRequestListener;
import com.hybris.mobile.lib.http.response.Response;

import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Sync adapter for the catalog
 */
public abstract class CatalogSyncAdapter extends AbstractThreadedSyncAdapter {

    private static final String TAG = CatalogSyncAdapter.class.getCanonicalName();
    private static String AUTHORITY;

    protected ContentServiceHelper mContentServiceHelper;
    private NotificationManager mNotifyManager;
    private Notification mNotification;
    private int mNotificationId = new Random().nextInt();
    private AtomicInteger mNbCalls;
    private CountDownLatch mBlockSync;
    // Used to know when the sync is done
    private OnRequestListener mOnRequestListenerEndOfSync = new OnRequestListener() {

        @Override
        public void beforeRequest() {
            mNbCalls.incrementAndGet();
        }

        @Override
        public void afterRequestBeforeResponse() {

        }

        @Override
        public void afterRequest(boolean isDataSynced) {
            if (mNbCalls.decrementAndGet() == 0) {
                showNotificationProgress(false);

                if (mBlockSync != null) {
                    mBlockSync.countDown();
                }
            }
        }
    };

    public CatalogSyncAdapter(Context context, boolean autoInitialize, ContentServiceHelper contentServiceHelper) {
        super(context, autoInitialize);
        mContentServiceHelper = contentServiceHelper;

        // Notification
        mNotifyManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
        Notification.Builder builder = new Notification.Builder(getContext());
        builder.setContentTitle(getContext().getString(R.string.sync_notification_title))
                .setContentText(getContext().getString(R.string.sync_notification_description))
                .setSmallIcon(R.drawable.ic_provider);
        builder.setProgress(0, 0, true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            mNotification = builder.build();
        } else {
            mNotification = builder.getNotification();
        }

        // Authority & Account
        AUTHORITY = contentServiceHelper.getConfiguration().getCatalogAuthority();
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider,
            SyncResult syncResult) {

        Log.i(TAG, "Receiving a sync with bundle: " + extras.toString());

        // Get some optional parameters
        String categoryId = extras.getString(CatalogSyncConstants.SYNC_PARAM_GROUP_ID);
        String productId = extras.getString(CatalogSyncConstants.SYNC_PARAM_DATA_ID);
        boolean loadVariants = extras.getBoolean(CatalogSyncConstants.SYNC_PARAM_LOAD_VARIANTS);
        String contentServiceHelperUrl = extras
                .getString(CatalogSyncConstants.SYNC_PARAM_CONTENT_SERVICE_HELPER_URL);
        boolean cancelAllRequests = extras.getBoolean(CatalogSyncConstants.SYNC_PARAM_CANCEL_ALL_REQUESTS);

        // Update the content service helper url
        if (StringUtils.isNotBlank(contentServiceHelperUrl)) {
            String catalog = extras.getString(CatalogSyncConstants.SYNC_PARAM_CONTENT_SERVICE_HELPER_CATALOG);
            String catalogId = extras.getString(CatalogSyncConstants.SYNC_PARAM_CONTENT_SERVICE_HELPER_CATALOG_ID);
            String catalogVersionId = extras
                    .getString(CatalogSyncConstants.SYNC_PARAM_CONTENT_SERVICE_HELPER_CATALOG_VERSION_ID);
            String catalogMainCategoryId = extras
                    .getString(CatalogSyncConstants.SYNC_PARAM_CONTENT_SERVICE_HELPER_MAIN_CATEGORY_ID);
            updateContentServiceHelperUrlConfiguration(contentServiceHelperUrl, catalog, catalogId,
                    catalogVersionId, catalogMainCategoryId);
        }
        // Cancelling all the requests
        else if (cancelAllRequests) {
            cancelAllRequests();
        }
        // Sync a category
        else if (StringUtils.isNotBlank(categoryId)) {
            Log.i(TAG, "Syncing the category " + categoryId);

            int currentPage = 0;
            int pageSize = 0;

            if (extras.containsKey(CatalogSyncConstants.SYNC_PARAM_CURRENT_PAGE)
                    && extras.containsKey(CatalogSyncConstants.SYNC_PARAM_PAGE_SIZE)) {
                currentPage = extras.getInt(CatalogSyncConstants.SYNC_PARAM_CURRENT_PAGE);
                pageSize = extras.getInt(CatalogSyncConstants.SYNC_PARAM_PAGE_SIZE);
            }

            syncCategory(categoryId, currentPage, pageSize);

        }
        // Sync a product
        else if (StringUtils.isNotBlank(productId)) {
            Log.i(TAG, "Syncing the product " + productId);

            loadProduct(productId, categoryId, null, false, loadVariants);
        }
        // Sync all the catalog
        else {
            Log.i(TAG, "Syncing the catalog");

            // Init nb calls counter and blocker
            mNbCalls = new AtomicInteger();
            mBlockSync = new CountDownLatch(1);

            String categories = extras.getString(CatalogSyncConstants.SYNC_PARAM_GROUP_ID_LIST);

            try {
                String[] categoryList = null;

                if (StringUtils.isNotBlank(categories)) {
                    categoryList = categories.split(CatalogSyncConstants.SYNC_PARAM_GROUP_ID_LIST_SEPARATOR);
                }

                syncCatalog(categoryList);

                // Save the date
                mContentServiceHelper.saveCatalogLastSyncDate(new Date().getTime());

                // Showing the notification
                showNotificationProgress(true);

                // Wait for the end of the sync
                mBlockSync.await(getContext().getResources().getInteger(R.integer.sync_timeout_in_min),
                        TimeUnit.MINUTES);

            } catch (InterruptedException e) {
                Log.e(TAG, "Error syncing the catalog");
            }
        }
    }

    /**
     * Sync a specific category
     *
     * @param categoryId  Unique Identifier for a specific Category of product
     * @param currentPage Page Number to be received in response
     * @param pageSize    Amount of product per page
     * @throws InterruptedException
     */
    private void syncCategory(final String categoryId, int currentPage, int pageSize) {
        Log.i(TAG, "Syncing the category " + categoryId + ", page " + currentPage + " and page size " + pageSize);

        QueryProducts queryProducts = new QueryProducts();
        queryProducts.setIdCategory(categoryId);

        if (currentPage != 0) {
            queryProducts.setCurrentPage(currentPage);
        }

        if (pageSize != 0) {
            queryProducts.setPageSize(pageSize);
        }

        getProducts(queryProducts, false, null, new Callback() {
            @Override
            public void onProductsLoadedSuccess(List<DataSync> products, Long total) {

                Log.i(TAG, "Response received after syncing the category " + categoryId);

                List<String> listCategory = new ArrayList<>();
                listCategory.add(categoryId);

                // Saving all the products
                if (products != null && !products.isEmpty()) {
                    for (DataSync product : products) {
                        saveProduct(product, listCategory, false);
                    }
                }

                // Update the sync status for the category
                ContentValues contentValues = new ContentValues();
                contentValues.put(CatalogContract.DataBaseSyncStatusGroup.ATT_GROUP_ID, categoryId);
                contentValues.put(CatalogContract.DataBaseSyncStatusGroup.ATT_STATUS,
                        SyncStatus.UPTODATE.getValue());
                getContext().getContentResolver().update(
                        Uri.withAppendedPath(CatalogContract.Provider.getUriSyncGroup(AUTHORITY), categoryId),
                        contentValues, null, null);

                // Notify the content provider
                Uri uriToNotify = Uri.withAppendedPath(CatalogContract.Provider.getUriGroup(AUTHORITY), categoryId);

                Log.i(TAG, "Notify category changes for " + uriToNotify);
                getContext().getContentResolver().notifyChange(uriToNotify, null);

            }

            @Override
            public void onProductsLoadedError() {

            }
        });
    }

    /**
     * Sync the catalog
     *
     * @param categoryList List of categories
     * @throws InterruptedException
     */
    private void syncCatalog(final String[] categoryList) {
        Log.i(TAG, "Syncing the catalog for categories " + Arrays.toString(categoryList));

        QueryCatalog queryCatalog = new QueryCatalog();
        queryCatalog.setCatalogId(mContentServiceHelper.getConfiguration().getCatalogId());
        queryCatalog.setCatalogVersionId(mContentServiceHelper.getConfiguration().getCatalogVersionId());
        queryCatalog.setCatalogCategoryId(mContentServiceHelper.getConfiguration().getCatalogIdMainCategory());

        // Getting all the catalog with the cache parameter to true
        mContentServiceHelper.getCatalogCategory(new ResponseReceiver<CategoryHierarchy>() {

            @Override
            public void onResponse(Response<CategoryHierarchy> response) {
                Log.i(TAG, "Response received after syncing the catalog");

                // Because we cache this call - see the parameter true - we get first the cached data
                // We do the work only with the synced data
                if (response.isSync()) {

                    // Create the links with the parent for all the categories
                    if (response.getData() != null && response.getData().getSubcategories() != null) {
                        for (CategoryHierarchy categoryHierarchy : response.getData().getSubcategories()) {
                            categoryHierarchy.setParent(response.getData());
                        }
                    }

                    // We loop specific categories
                    if (categoryList != null && categoryList.length > 0) {
                        Log.i(TAG, "Syncing the categories: " + Arrays.toString(categoryList));

                        for (final String aCategoryList : categoryList) {
                            if (StringUtils.isNotBlank(aCategoryList)) {
                                // Finding the category that matches the id
                                CategoryHierarchy categoryHierarchy = getCategory(response.getData(),
                                        aCategoryList);

                                if (categoryHierarchy != null) {
                                    // Deleting the product-category links first
                                    getContext().getContentResolver()
                                            .delete(Uri.withAppendedPath(
                                                    CatalogContract.Provider.getUriGroup(AUTHORITY),
                                                    categoryHierarchy.getId()), null, null);

                                    Log.i(TAG, "Syncing the category " + categoryHierarchy.getId());
                                    loopCategory(categoryHierarchy, 0, false, true);
                                }
                            }
                        }
                    }
                    // We loop through all the categories
                    else {
                        // Deleting all the product-category links first
                        getContext().getContentResolver().delete(CatalogContract.Provider.getUriGroup(AUTHORITY),
                                null, null);

                        Log.i(TAG, "Syncing the entire catalog");
                        loopCategory(response.getData(), 0, true, true);
                    }

                    // We send a message to update the cache on the app
                    LocalBroadcastManager.getInstance(getContext())
                            .sendBroadcast(new Intent(getContext().getString(R.string.intent_action_update_cache)));
                }
            }

            @Override
            public void onError(Response<ErrorList> response) {
                Log.e(TAG, ErrorUtils.getFirstErrorMessage(response.getData()));
            }
        }, null, queryCatalog, true, null, null);
    }

    /**
     * Find in the category and subcategories, the category that matches the id
     *
     * @param categoryHierarchy Category to be searched
     * @param categoryId        Unique Identifier for a specific Category of product
     * @return Category found
     */
    private CategoryHierarchy getCategory(CategoryHierarchy categoryHierarchy, String categoryId) {

        if (StringUtils.equals(categoryHierarchy.getId(), categoryId)) {
            return categoryHierarchy;
        } else if (categoryHierarchy.getSubcategories() != null) {
            CategoryHierarchy categoryHierarchyToFind = null;
            int i = 0;

            while (categoryHierarchyToFind == null && i < categoryHierarchy.getSubcategories().size()) {
                categoryHierarchyToFind = getCategory(categoryHierarchy.getSubcategories().get(i), categoryId);
                i++;
            }

            return categoryHierarchyToFind;
        }

        return null;
    }

    /**
     * Loop and save the products and sub-categories of a specific category
     *
     * @param categoryHierarchy Category to be searched
     * @param currentPage       Page Number to be received in response
     * @param loopSubCategories Subcategory to be searched
     * @param loadVariants      True Load variant else false
     */
    private void loopCategory(final CategoryHierarchy categoryHierarchy, final int currentPage,
            final boolean loopSubCategories, final boolean loadVariants) {
        Log.i(TAG, "Syncing the products of the category " + categoryHierarchy.getId() + " for page " + currentPage
                + " to " + Constants.CATALOG_MAX_PAGE_SIZE);

        final QueryProducts queryProducts = new QueryProducts();
        queryProducts.setIdCategory(categoryHierarchy.getId());
        queryProducts.setCurrentPage(currentPage);
        queryProducts.setPageSize(Constants.CATALOG_MAX_PAGE_SIZE);

        getProducts(queryProducts, false, mOnRequestListenerEndOfSync, new Callback() {
            @Override
            public void onProductsLoadedSuccess(List<DataSync> products, Long total) {
                Log.i(TAG, "Response received after syncing the products of the category "
                        + categoryHierarchy.getId());

                // Construct the list of the parent category ids
                List<String> parentCategoriesId = getParentsCategoriesIds(categoryHierarchy,
                        new ArrayList<String>());

                // Loading the products
                if (products != null) {
                    for (DataSync product : products) {
                        loadProduct(product.getId(), categoryHierarchy.getId(), parentCategoriesId, true,
                                loadVariants);
                    }
                }

                // Loading the next pages of products and subcategories
                if (currentPage == 0) {

                    int nbPages = (int) Math.ceil(total.intValue() / queryProducts.getPageSize());

                    // Get the next pages of the category
                    for (int i = 1; i < nbPages; i++) {
                        loopCategory(categoryHierarchy, i, loopSubCategories, loadVariants);
                    }

                    // Get the subcategories
                    if (loopSubCategories && categoryHierarchy.getSubcategories() != null) {
                        for (CategoryHierarchy subCategoryHierarchy : categoryHierarchy.getSubcategories()) {
                            loopCategory(subCategoryHierarchy, 0, loopSubCategories, loadVariants);
                        }
                    }

                }
            }

            @Override
            public void onProductsLoadedError() {

            }
        });

    }

    /**
     * Load a product
     *
     * @param productCode              Unique Product Identifier
     * @param categoryId               Unique Identifier for a specific Category of product
     * @param parentCategoriesIds      List of Parent Category Identifier in String format
     * @param hideProgressNotification @throws InterruptedException
     * @param loadVariants             True Load variant else false
     */
    private void loadProduct(final String productCode, final String categoryId,
            final List<String> parentCategoriesIds, final boolean hideProgressNotification,
            final boolean loadVariants) {
        Log.i(TAG, "Syncing the product " + productCode + " for category " + categoryId);

        QueryProduct queryProduct = new QueryProduct();
        queryProduct.setCode(productCode);

        OnRequestListener onRequestListener = null;

        // Hide the progress notification when no more products need to be loaded
        if (hideProgressNotification) {
            onRequestListener = mOnRequestListenerEndOfSync;
        }

        mContentServiceHelper.getProduct(new ResponseReceiver<Product>() {

            @Override
            public void onResponse(Response<Product> response) {

                Log.i(TAG, "Response received after syncing the product " + productCode);

                List<String> listCategory = new ArrayList<>();
                listCategory.add(categoryId);

                if (parentCategoriesIds != null && !parentCategoriesIds.isEmpty()) {
                    listCategory.addAll(parentCategoriesIds);
                }

                Product product = response.getData();

                // Saving the products
                try {
                    saveProduct(
                            new DataSync(product.getCode(),
                                    mContentServiceHelper.getDataConverter().convertTo(product)),
                            listCategory, true);
                } catch (DataConverterException e) {
                    Log.e(TAG, "Error converting the product to a data String. Error: " + e.getLocalizedMessage());
                }

                // Loading the variants
                if (loadVariants && product.getVariantOptions() != null && !product.getVariantOptions().isEmpty()) {
                    Log.i(TAG, "Syncing the variants for the product " + productCode);

                    for (VariantOption variantOption : product.getVariantOptions()) {

                        // The variants are not linked to any category
                        loadProduct(variantOption.getCode(), null, null, hideProgressNotification, false);
                    }
                }

            }

            @Override
            public void onError(Response<ErrorList> response) {
                Log.e(TAG, ErrorUtils.getFirstErrorMessage(response.getData()));
            }
        }, null, queryProduct, false, null, onRequestListener);
    }

    /**
     * Save the product by calling the content provider
     *
     * @param product     Product to be saved
     * @param categoryIds List of Category Identifier in String format
     * @param isDetails   true if product detail call else false for product with less info
     */
    private void saveProduct(DataSync product, List<String> categoryIds, boolean isDetails) {
        Log.i(TAG, "Saving the product " + product.getId() + " for category " + categoryIds);

        // Saving the link product - categories
        for (String idCategory : categoryIds) {
            Log.i(TAG, "Saving the link category " + idCategory + " - product" + product.getId());
            ContentValues contentValues = new ContentValues();
            contentValues.put(CatalogContract.DataBaseDataLinkGroup.ATT_GROUP_ID, idCategory);
            contentValues.put(CatalogContract.DataBaseDataLinkGroup.ATT_DATA_ID, product.getId());
            getContext().getContentResolver().insert(
                    Uri.withAppendedPath(CatalogContract.Provider.getUriGroup(AUTHORITY), product.getId()),
                    contentValues);
        }

        // Saving the product
        ContentValues contentValues = new ContentValues();
        contentValues.put(CatalogContract.DataBaseData.ATT_DATA_ID, product.getId());
        contentValues.put(CatalogContract.DataBaseData.ATT_DATA, product.getData());
        contentValues.put(CatalogContract.DataBaseData.ATT_STATUS, SyncStatus.UPTODATE.getValue());

        getContext().getContentResolver().insert(
                Uri.withAppendedPath(CatalogContract.Provider.getUriData(AUTHORITY), product.getId()),
                contentValues);

        if (isDetails) {
            getContext().getContentResolver().insert(
                    Uri.withAppendedPath(CatalogContract.Provider.getUriDataDetails(AUTHORITY), product.getId()),
                    contentValues);
        }
    }

    /**
     * This method update the url configuration of the content service helper
     *
     * @param url                   the new url
     * @param catalog               the catalog
     * @param catalogId             the catalog id
     * @param catalogVersionId      the catalog version id
     * @param catalogMainCategoryId the main category id of the catalo
     */
    public void updateContentServiceHelperUrlConfiguration(String url, String catalog, String catalogId,
            String catalogVersionId, String catalogMainCategoryId) {
        mContentServiceHelper.updateConfiguration(url, catalog, catalogId, catalogVersionId, catalogMainCategoryId);
    }

    /**
     * Cancel all the current requests to the content service helper
     */
    public void cancelAllRequests() {
        mContentServiceHelper.cancelAll();
    }

    /**
     * Show the notification download progress
     *
     * @param show True notification will be displayed else hidden
     */
    private void showNotificationProgress(boolean show) {
        // Showing the notification
        if (show) {
            mNotifyManager.notify(mNotificationId, mNotification);
        } else {
            mNotifyManager.cancel(mNotificationId);
        }
    }

    /**
     * Return the list of the parent's categories ids of the category
     *
     * @param categoryHierarchy Category where parent id will be searched
     * @param results           List of parent Id
     * @return list of the parent's categories ids of the category
     */
    private List<String> getParentsCategoriesIds(CategoryHierarchy categoryHierarchy, List<String> results) {

        if (results == null) {
            results = new ArrayList<>();
        }

        if (categoryHierarchy.getParent() != null) {
            results.add(categoryHierarchy.getParent().getId());
            return getParentsCategoriesIds(categoryHierarchy.getParent(), results);
        }

        return results;
    }

    public abstract void getProducts(QueryProducts queryProducts, boolean shouldUseCache,
            OnRequestListener onRequestListener, Callback callback);

    public interface Callback {
        void onProductsLoadedSuccess(List<DataSync> products, Long total);

        void onProductsLoadedError();
    }
}