package com.tesora.dve.common; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.math.RandomUtils; import org.apache.log4j.Logger; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.tesora.dve.common.PEUrl; import com.tesora.dve.exceptions.PEException; /** * This class manages a list of URLs that are provided via the initialize() method. The getUrl() method * is used to retrieve a URL from the. As currently implemented, it will return one randomly from the * list. If the caller decides the URL has "failed", a call to failUrl() is made. This will put the URL * into a failed list. The failed list has an expiry elapsed time (also provided to initialize()) that will * cause the URLs to be evicted from the fail list after the time has passed. After eviction, they will * be available for the getUrl() call to use. * */ public final class UrlBalancer{ static final Logger logger = Logger.getLogger(UrlBalancer.class); private List<PEUrl> managedUrlList = Collections.synchronizedList(new ArrayList<PEUrl>()); private Cache<String, PEUrl> failedUrlCache; private static final UrlBalancer INSTANCE = new UrlBalancer(); private UrlBalancer() {} /** * Initializes the singleton instance of this class. Any previous state will be cleared. * * @param failedUrlExpiration - int - number of seconds to elapse after failUrl() is called before * the Url is available to getUrl() again * @param providedUrlList - List<String> - a List containing the string representation of valid URLs * @throws PEException * - if the providedUrlList is empty * - if any of the URLs in the list are invalid */ public void initialize(int failedUrlExpiration, List<String> providedUrlList) throws PEException { synchronized (managedUrlList) { if ( logger.isDebugEnabled() ) logger.debug("Initializing URL Balancer with failed URL expiry of " + failedUrlExpiration); close(); failedUrlCache = CacheBuilder.newBuilder() .expireAfterWrite(failedUrlExpiration, TimeUnit.SECONDS) .build(); for (String urlStr : providedUrlList) { managedUrlList.add(PEUrl.fromUrlString(urlStr)); if ( logger.isDebugEnabled() ) logger.debug("URL Balancer initialized with '" + urlStr + "'"); } if (managedUrlList.isEmpty()) throw new PEException( "No addresses were added to the Active URL List from provided list"); } } /** * Clears all state held within the UrlBalancer */ public void close() { synchronized (managedUrlList) { managedUrlList.clear(); if (failedUrlCache != null) { failedUrlCache.invalidateAll(); failedUrlCache = null; } } } /** * Returns a single URL from the list. Will not return any URLs that are currently failed. * * @return - PEUrl - a random URL from the list * @throws PEException * - URL list is empty * - all the URLs in the list are failed */ public PEUrl getUrl() throws PEException { if (managedUrlList.isEmpty()) throw new PEException("Managed URL List doesn't contain any entries"); PEUrl urlCandidate = null; boolean urlNotFound = true; while ( urlNotFound ) { urlCandidate = managedUrlList.get(RandomUtils.nextInt(managedUrlList.size())); if ( failedUrlCache.getIfPresent(urlCandidate.toString()) == null ) { urlNotFound = false; } if (managedUrlList.size() == failedUrlCache.size() ) throw new PEException("All URLs in managed list have been marked as failed"); } if ( logger.isDebugEnabled() ) logger.debug("URL Balancer returning URL " + urlCandidate.getURL()); return urlCandidate; } /** * Returns the current size of the URL list. Will include any URLs that were failed. * * @return int */ public int size() { return managedUrlList.size(); } /** * Used to mark urlToFail as currently being failed. Once failed this URL will not be used by * the getURL() call again until the failedUrlExpiration time has passed. * * @param urlToFail - PEUrl - the URL to mark as failed * @throws PEException * - if urlToFail isn't in the list */ public void failUrl(PEUrl urlToFail) throws PEException { if ( !managedUrlList.contains(urlToFail) ) throw new PEException("The URL marked to fail isn't in the managed list (" + urlToFail + ")"); failedUrlCache.put(urlToFail.toString(), urlToFail); if ( logger.isDebugEnabled() ) logger.debug("URL Balancer is tracking '" + urlToFail + "' as failed"); } /** * Return the singleton instance of the UrlBalancer * * @return - UrlBalancer */ public static UrlBalancer getInstance() { return INSTANCE; } }