com.seajas.search.contender.replication.TaxonomyCache.java Source code

Java tutorial

Introduction

Here is the source code for com.seajas.search.contender.replication.TaxonomyCache.java

Source

/**
 * Copyright (C) 2013 Seajas, the Netherlands.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.seajas.search.contender.replication;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.seajas.search.bridge.profiler.ConversionUtils;
import com.seajas.search.bridge.profiler.model.taxonomy.TaxonomyNode;
import com.seajas.search.profiler.wsdl.taxonomy.ITaxonomyCreation;
import com.seajas.search.profiler.wsdl.taxonomy.ITaxonomyListing;

/**
 * Read-most taxonomy cache implementation.
 * 
 * @author Jasper van Veghel <jasper@seajas.com>
 */
@Component
public class TaxonomyCache {
    /**
     * The logger.
     */
    private static final Logger logger = LoggerFactory.getLogger(TaxonomyCache.class);

    /**
     * Taxonomy nodes by their IDs.
     */
    private volatile Map<Integer, TaxonomyNode> nodesById;

    /**
     * Taxonomy nodes by their match-names.
     */
    private volatile Map<String, TaxonomyNode> nodesByMatch;

    /**
     * Reentrant read-write lock.
     */
    private volatile ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * The field prefix.
     */
    @Value("${contender.project.taxonomy.field.prefix}")
    private String fieldPrefix;

    /**
     * The taxonomy creation service.
     */
    @Autowired
    public ITaxonomyCreation taxonomyCreationService;

    /**
     * Bootstrap the cache.
     * 
     * @param taxonomyListingService
     */
    @Autowired
    public void setTaxonomyService(final ITaxonomyListing taxonomyListingService) {
        if (!ConversionUtils.validateTaxonomyModelIntegrity())
            throw new IllegalStateException(
                    "Object copying does not account for all fields - model definition too new");

        nodesById = new HashMap<Integer, TaxonomyNode>();
        nodesByMatch = new HashMap<String, TaxonomyNode>();

        for (com.seajas.search.bridge.profiler.wsdl.taxonomy.TaxonomyNode node : taxonomyListingService
                .retrieveAllNodes()) {
            TaxonomyNode internalNode = ConversionUtils.convertTaxonomyWsToInternal(node);

            nodesById.put(node.getId(), internalNode);

            if (StringUtils.hasText(node.getMatch()))
                nodesByMatch.put(node.getMatch().trim().toLowerCase(), internalNode);
        }
    }

    /**
     * Process an incoming update.
     * 
     * @param node
     */
    public void update(final TaxonomyNode node) {
        lock.writeLock().lock();

        try {
            nodesById.put(node.getId(), node);

            // Always remove the old entry, as the match name may have been updated

            String matchToRemove = null;

            for (Entry<String, TaxonomyNode> entry : nodesByMatch.entrySet())
                if (entry.getValue().getId().equals(node.getId())) {
                    matchToRemove = entry.getKey();

                    break;
                }

            if (matchToRemove != null)
                nodesByMatch.remove(matchToRemove);

            // Then add it back up, if non-empty

            if (StringUtils.hasText(node.getMatch()))
                nodesByMatch.put(node.getMatch().trim().toLowerCase(), node);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * Process an incoming update.
     * 
     * @param id
     */
    public void delete(final Integer id) {
        lock.writeLock().lock();

        try {
            TaxonomyNode node = nodesById.get(id);

            if (node != null && StringUtils.hasText(node.getMatch()))
                nodesByMatch.remove(node.getMatch().trim().toLowerCase());

            nodesById.remove(id);

            // Now walk the nodesById non-recursively and remove all nodes without a valid parent

            Integer modified, iteration = 0;

            do {
                modified = 0;

                List<TaxonomyNode> removalNodes = new ArrayList<TaxonomyNode>();

                for (Entry<Integer, TaxonomyNode> nodeById : nodesById.entrySet())
                    if (nodeById.getValue().getParentId() != null && nodeById.getValue().getParentId() > 3
                            && !nodesById.containsKey(nodeById.getValue().getParentId()))
                        removalNodes.add(nodeById.getValue());

                for (TaxonomyNode removalNode : removalNodes) {
                    nodesById.remove(removalNode.getId());
                    nodesByMatch.remove(removalNode.getMatch());
                }

                modified += removalNodes.size();

                if (modified > 0 && logger.isInfoEnabled())
                    logger.info("Removed " + modified + " child node(s) after dropping ID " + id + " in iteration "
                            + iteration);

                iteration++;
            } while (modified != 0);
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * Retrieve a list of IDs for a given match.
     * 
     * @param match
     * @return List<Integer>
     */
    public List<Integer> getIdsByMatch(final String match) {
        lock.readLock().lock();

        try {
            List<Integer> result = new ArrayList<Integer>();

            if (nodesByMatch.containsKey(match.trim().toLowerCase())) {
                TaxonomyNode node = nodesByMatch.get(match.trim().toLowerCase());

                do {
                    result.add(node.getId());
                } while ((node = nodesById.get(node.getParentId())) != null);

                Collections.reverse(result);

                return result;
            } else
                return null;
        } finally {
            lock.readLock().unlock();
        }
    }

    /**
     * Add the given taxonomy match and job name to the list of unassigned matches.
     * 
     * @param jobName
     * @param taxonomyMatch
     * @return List<Integer>
     */
    public List<Integer> addToUnassigned(final String jobName, final String taxonomyMatch) {
        return taxonomyCreationService.addToUnassigned(taxonomyMatch, jobName);
    }

    /**
     * Retrieve the search index field prefix.
     * 
     * @return String
     */
    public String getFieldPrefix() {
        return StringUtils.hasText(fieldPrefix) ? fieldPrefix.trim() : "";
    }
}