Java tutorial
/** * 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() : ""; } }