All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sphere.internal.CategoryTreeImpl Maven / Gradle / Ivy

There is a newer version: 0.72.1
Show newest version
package io.sphere.internal;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import io.sphere.client.model.QueryResult;
import io.sphere.client.model.products.BackendCategory;
import io.sphere.internal.util.Concurrent;
import io.sphere.internal.util.Log;
import io.sphere.client.shop.CategoryTree;
import io.sphere.client.shop.model.Category;
import io.sphere.internal.util.Util;
import io.sphere.internal.util.ValidationE;
import net.jcip.annotations.GuardedBy;

import java.util.*;
import java.util.concurrent.*;

/** Fetches and builds the category tree in the background.
 *  Blocks on first read if the tree is still being fetched.  */
public class CategoryTreeImpl implements CategoryTree {
    Categories categoryService;
    private final Locale locale;
    private final Object categoriesLock = new Object();

    @GuardedBy("categoriesLock")
    private Optional> categoriesResult = Optional.absent();

    /** Allows at most one rebuild operation running in the background. */
    private final ThreadPoolExecutor refreshExecutor = Concurrent.singleTaskExecutor("Sphere-CategoryTree-refresh");

    private CategoryTreeImpl(Categories categoryService, Locale locale) {
        this.categoryService = categoryService;
        this.locale = locale;
    }

    public static CategoryTreeImpl createAndBeginBuildInBackground(Categories categoryService, Locale locale) {
        CategoryTreeImpl categoryTree = new CategoryTreeImpl(categoryService, locale);
        categoryTree.beginRebuild();
        return categoryTree;
    }

    @Override public List getRoots() { return getCache().getRoots(); }

    @Override
    public List getRoots(Comparator comparator) {
        return Ordering.from(comparator).immutableSortedCopy(getRoots());
    }

    @Override public Category getById(String id) { return getCache().getById(id); }
    @Override public Category getBySlug(String slug) { return getCache().getBySlug(slug); }

    @Override
    public Category getBySlug(String slug, Locale locale) {
        return getCache().getBySlug(slug, locale);
    }

    @Override public List getAsFlatList() { return getCache().getAsFlatList(); }

    @Override
    public void rebuildAsync() {
        beginRebuild();
    }

    /** Root categories (the ones that have no parent).*/
    private CategoryCache getCache() {
        synchronized (categoriesLock) {
            while (!categoriesResult.isPresent()) {
                try {
                    categoriesLock.wait();
                } catch (InterruptedException e) { }
            }
            if (categoriesResult.get().isError()) {
                beginRebuild();   // retry on error (essential to recover from backend errors)
                throw categoriesResult.get().getError();
            }
            return categoriesResult.get().getValue();
        }
    }

    /** Starts rebuild in the background. */
    private void beginRebuild() {
        try {
            Log.debug("[cache] Refreshing category tree.");
            refreshExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    List categories = Lists.newLinkedList();
                    try {
                        fetchCategories(categoryService, 0, categories);
                    } catch (Exception e) {
                        update(null, locale, e);
                        return;
                    }
                    update(categories, locale, null);
                }
            });
        } catch (RejectedExecutionException e) {
            // another rebuild is already in progress, ignore this one
        }
    }

    /**
     * Fetches all categories starting by a specific page.
     * @param categoryService
     * @param page the current offset
     * @param accumulator output parameter, the currently obtained categories
     */
    private void fetchCategories(final Categories categoryService, final int page, final List accumulator) {
        final int pageSize = 500;
        QueryResult result = categoryService.query().page(page).pageSize(pageSize).fetch();
        accumulator.addAll(result.getResults());
        final int numberOfItemsAlreadyFetched = (page + 1) * pageSize;
        final boolean hasMore = result.getTotal() > numberOfItemsAlreadyFetched;
        if (hasMore) {
            fetchCategories(categoryService, page + 1, accumulator);
        }
    }



    /** Sets result after rebuild. */
    private void update(List backendCategories, Locale locale, Exception e) {
        CategoryCache categoriesCache = null;
        if (e != null) {
            Log.error("[cache] Couldn't initialize category tree", e);
        }  else {
            categoriesCache = CategoryCache.create(Category.buildTree(backendCategories), locale);
        }
        synchronized (categoriesLock) {
            if (e == null) {
                this.categoriesResult = Optional.of(ValidationE.success(categoriesCache));
            } else {
                this.categoriesResult = Optional.of(ValidationE.error(Util.toSphereException(e)));
            }
            categoriesLock.notifyAll();
        }
        if (e != null) {
            Log.debug("[cache] Refreshed category tree.");
        }
    }

    /** Shuts down internal thread pools. */
    public void shutdown() {
        refreshExecutor.shutdownNow();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy