/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jumpmind.symmetric.transport.http; import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jumpmind.extension.IBuiltInExtensionPoint; import org.jumpmind.symmetric.common.Constants; import org.jumpmind.symmetric.service.IBandwidthService; import org.jumpmind.symmetric.service.INodeService; import org.jumpmind.symmetric.transport.ISyncUrlExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This {@link ISyncUrlExtension} is capable of measuring the bandwidth of a * list of urls in order to select the one with the most bandwidth for use. * <p/> * Use the URI notation of: * ext://httpBandwidthUrlSelector?1=http://url.1.com&2=http://url.2.com¶m=value * <p/> * Valid parameters are constants on this class that start with PARAM_. Any * parameter that is a numeral will be designated a possible URL. */ public class HttpBandwidthUrlSelector implements ISyncUrlExtension, IBuiltInExtensionPoint { protected final Logger log = LoggerFactory.getLogger(getClass()); public static String PARAM_PRELOAD_ONLY = "initialLoadOnly"; public static String PARAM_SAMPLE_SIZE = "sampleSize"; public static String PARAM_SAMPLE_TTL = "sampleTTL"; public static String PARAM_MAX_SAMPLE_DURATION = "maxSampleDuration"; private long defaultSampleSize = 1000; private long defaultSampleTTL = 60000; private long defaultMaxSampleDuration = 2000; protected long lastSampleTs; private Map<URI, List<SyncUrl>> cachedUrls = new HashMap<URI, List<SyncUrl>>(); private INodeService nodeService; private IBandwidthService bandwidthService; public HttpBandwidthUrlSelector(INodeService nodeService, IBandwidthService bandwidthService) { this.nodeService = nodeService; this.bandwidthService = bandwidthService; } public String resolveUrl(URI uri) { if (uri.toString().startsWith(Constants.PROTOCOL_EXT)) { Map<String, String> params = getParameters(uri); List<SyncUrl> urls = null; if (!cachedUrls.containsKey(uri)) { urls = getUrls(params); cachedUrls.put(uri, urls); } else { urls = cachedUrls.get(uri); } boolean initialLoadOnly = isInitialLoadOnly(params); if ((initialLoadOnly && nodeService != null && !nodeService.isDataLoadCompleted()) || !initialLoadOnly) { long ts = System.currentTimeMillis(); if (ts - getSampleTTL(params) > lastSampleTs) { for (SyncUrl syncUrl : urls) { syncUrl.kbps = bandwidthService.getDownloadKbpsFor(syncUrl.url, getSampleSize(params), getMaxSampleDuration(params)); } lastSampleTs = ts; Collections.sort(urls, new BestBandwidthSorter()); } return urls.get(0).url; } else { Collections.sort(urls, new ListOrderSorter()); return urls.get(0).url; } } else return uri.toString(); } protected long getSampleSize(Map<String, String> params) { long sampleSize = this.defaultSampleSize; String val = params.get(PARAM_SAMPLE_SIZE); if (val != null) { try { sampleSize = Long.parseLong(val); } catch (NumberFormatException e) { log.error("Unable to parse sampleSize of {}", val); } } return sampleSize; } protected long getMaxSampleDuration(Map<String, String> params) { long maxSampleDuration = this.defaultMaxSampleDuration; String val = params.get(PARAM_MAX_SAMPLE_DURATION); if (val != null) { try { maxSampleDuration = Long.parseLong(val); } catch (NumberFormatException e) { log.error("Unable to parse sampleSize of {}",val); } } return maxSampleDuration; } protected long getSampleTTL(Map<String, String> params) { long sampleTTL = this.defaultSampleTTL; String val = params.get(PARAM_SAMPLE_TTL); if (val != null) { try { sampleTTL = Long.parseLong(val); } catch (NumberFormatException e) { log.error("Unable to parse sampleTTL of {}",val); } } return sampleTTL; } protected boolean isInitialLoadOnly(Map<String, String> params) { String val = params.get(PARAM_PRELOAD_ONLY); return val != null && val.equalsIgnoreCase("true"); } protected List<SyncUrl> getUrls(Map<String, String> params) { List<SyncUrl> urls = new ArrayList<SyncUrl>(); for (String key : params.keySet()) { try { int order = Integer.parseInt(key); urls.add(new SyncUrl(params.get(key), order)); } catch (NumberFormatException e) { } } return urls; } protected Map<String, String> getParameters(URI uri) { String query = uri.getQuery(); String[] params = query.split("&"); Map<String, String> map = new HashMap<String, String>(); for (String param : params) { String[] pair = param.split("="); if (pair.length > 1) { String name = pair[0]; String value = pair[1]; map.put(name, value); } } return map; } public void setDefaultSampleSize(long sampleSize) { this.defaultSampleSize = sampleSize; } public void setDefaultSampleTTL(long sampleTTL) { this.defaultSampleTTL = sampleTTL; } public void setDefaultMaxSampleDuration(long defaultMaxSampleDuration) { this.defaultMaxSampleDuration = defaultMaxSampleDuration; } class SyncUrl { String url; int order; double kbps; public SyncUrl(String url, int order) { super(); this.url = url; this.order = order; } } class ListOrderSorter implements Comparator<SyncUrl> { public int compare(SyncUrl o1, SyncUrl o2) { int thisVal = o1.order; int anotherVal = o2.order; return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); } } class BestBandwidthSorter implements Comparator<SyncUrl> { public int compare(SyncUrl o1, SyncUrl o2) { double thisVal = o1.kbps; double anotherVal = o2.kbps; return (thisVal > anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); } } }