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

io.apptik.comm.jus.RequestQueue Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 Apptik Project
 * Copyright (C) 2014 Kalin Maldzhanski
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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 io.apptik.comm.jus;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import io.apptik.comm.jus.RequestListener.ListenerFactory;
import io.apptik.comm.jus.auth.Authenticator;
import io.apptik.comm.jus.converter.BasicConverterFactory;
import io.apptik.comm.jus.toolbox.Utils;

import static io.apptik.comm.jus.Converter.Factory;
import static io.apptik.comm.jus.toolbox.Utils.checkNotNull;

/**
 * A request dispatch queue with a threadId pool of dispatchers.
 * 

* Calling {@link #add(Request)} will enqueue the given Request for dispatch, * resolving from either cache or network on a worker threadId, and then delivering * a parsed response on the main threadId. *

*/ public class RequestQueue { public static final String EVENT_CACHE_DISPATCHER_START = "cache_dispatcher_start"; public static final String EVENT_CACHE_DISPATCHER_STOP = "cache_dispatcher_stop"; public static final String EVENT_NETWORK_DISPATCHER_START = "network_dispatcher_start"; public static final String EVENT_NETWORK_DISPATCHER_STOP = "network_dispatcher_stop"; /** * Used for generating monotonically-increasing sequence numbers for requests. */ private AtomicInteger sequenceGenerator = new AtomicInteger(); /** * Staging area for requests that already have a duplicate request in flight. *

*

    *
  • containsKey(cacheKey) indicates that there is a request in flight for the given cache * key.
  • *
  • get(cacheKey) returns waiting requests for the given cache key. The in flight request * is not contained in that list. Is null if no requests are staged.
  • *
*/ private final Map>> waitingRequests = new ConcurrentHashMap<>(); /** * The set of all requests currently being processed by this RequestQueue. A Request * will be in this set if it is waiting in any queue or currently being processed by * any dispatcher. */ private final Set> currentRequests = Collections.newSetFromMap(new ConcurrentHashMap, Boolean>()); // = new HashSet>(); /** * The cache triage queue. */ protected final PriorityBlockingQueue> cacheQueue = new PriorityBlockingQueue<>(); /** * The queue of requests that are actually going out to the network. */ protected final PriorityBlockingQueue> networkQueue = new PriorityBlockingQueue<>(); /** * Number of network request dispatcher threads to start. */ public static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; /** * Cache interface for retrieving and storing responses. */ protected final Cache cache; /** * Network interface for performing requests. */ protected final Network network; /** * Response delivery mechanism. */ protected final ResponseDelivery delivery; /** * The network dispatchers. */ protected NetworkDispatcher[] networkDispatchers; /** * The cache dispatcher. */ protected CacheDispatcher cacheDispatcher; /** * Network dispatcher factory */ protected NetworkDispatcher.NetworkDispatcherFactory networkDispatcherFactory; private final List authenticatorFactories = new ArrayList<>(); private final List converterFactories = new ArrayList<>(); private final List requestTransformers = new ArrayList<>(); private final List responseTransformers = new ArrayList<>(); private final List listenerFactories = new ArrayList<>(); /** * Marker listeners for RequestQueue related events */ private final List markerListeners = new ArrayList<>(); private RetryPolicy.Factory retryPolicyFactory = null; private RedirectPolicy.Factory redirectPolicyFactory = null; private ConnectivityManager.Factory connectivityManagerFactory = null; private NoConnectionPolicy.Factory noConnectionPolicyFactory = null; /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create * @param delivery A ResponseDelivery interface for posting responses and errors */ public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { this.cache = cache; this.network = network; this.networkDispatchers = new NetworkDispatcher[threadPoolSize]; this.delivery = delivery; this.converterFactories.add(new BasicConverterFactory()); } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create */ public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Executor() { @Override public void execute(Runnable command) { Utils.checkNotNull(command, "command==null"); command.run(); } })); } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests */ public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } public RequestQueue withCacheDispatcher(CacheDispatcher cacheDispatcher) { if (this.cacheDispatcher != null) { this.cacheDispatcher.quit(); } this.cacheDispatcher = cacheDispatcher; return this; } public RequestQueue withNetworkDispatcherFactory(NetworkDispatcher.NetworkDispatcherFactory networkDispatcherFactory) { for (NetworkDispatcher networkDispatcher : networkDispatchers) { if (networkDispatcher != null) { networkDispatcher.quit(); } } this.networkDispatcherFactory = networkDispatcherFactory; return this; } /** * Create network dispatchers (and corresponding threads) up to the pool size. */ private void setUpNetworkDispatchers() { if (networkDispatcherFactory == null) { networkDispatcherFactory = new NetworkDispatcher.NetworkDispatcherFactory (networkQueue, network, cache, delivery); } for (int i = 0; i < networkDispatchers.length; i++) { networkDispatchers[i] = networkDispatcherFactory.create(); networkDispatchers[i].start(); addMarker(EVENT_NETWORK_DISPATCHER_START, networkDispatchers[i]); } } /** * Starts the dispatchers in this queue. */ public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. if (cacheDispatcher == null) { cacheDispatcher = new CacheDispatcher(cacheQueue, networkQueue, cache, delivery); } cacheDispatcher.start(); addMarker(EVENT_CACHE_DISPATCHER_START, cacheDispatcher); // Create network dispatchers (and corresponding threads) up to the pool size. setUpNetworkDispatchers(); } /** * Stops the queue when all requests are finished */ public void stopWhenDone() { Thread t = new Thread(new Runnable() { @Override public void run() { while (getCurrentRequests() > 0) { try { Thread.sleep(33); } catch (InterruptedException e) { e.printStackTrace(); } } } }); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } stop(); } /** * Add marker listener for RequestQueue related events */ public R addQueueMarkerListener( RequestListener.MarkerListener markerListener) { if (markerListener != null) { synchronized (markerListeners) { this.markerListeners.add(markerListener); } } return (R) this; } /** * Remove marker listener for RequestQueue related events */ public R removeQueueMarkerListener(RequestListener.MarkerListener markerListener) { synchronized (markerListeners) { this.markerListeners.remove(markerListener); } return (R) this; } /** * Adds a marker for the {@link RequestQueue} * @param tag * @param args * @param * @return */ public R addMarker(String tag, Object... args) { Marker marker = new Marker(tag, Thread.currentThread().getId(), Thread.currentThread().getName(), System.nanoTime()); for (RequestListener.MarkerListener markerListener : markerListeners) { markerListener.onMarker(marker, args); } return (R) this; } /** * Stops the cache and network dispatchers. */ public void stop() { if (cacheDispatcher != null) { cacheDispatcher.quit(); addMarker(EVENT_CACHE_DISPATCHER_STOP, cacheDispatcher); } for (NetworkDispatcher netDispatcher : networkDispatchers) { if (netDispatcher != null) { netDispatcher.quit(); addMarker(EVENT_NETWORK_DISPATCHER_STOP, netDispatcher); } } } /** * Gets a sequence number. */ public int getSequenceNumber() { return sequenceGenerator.incrementAndGet(); } /** * Gets the {@link Cache} instance being used. */ public Cache getCache() { return cache; } /** * Cancels all requests in this queue for which the given filter applies. * * @param filter The filtering function to use */ public void cancelAll(RequestFilter filter) { synchronized (currentRequests) { for (Request request : currentRequests) { if (filter.apply(request)) { request.cancel(); } } } } /** * Cancels all requests in this queue with the given tag. Tag must be non-null * and equality is by identity. */ public void cancelAll(final Object tag) { if (tag == null) { throw new IllegalArgumentException("Cannot cancelAll with a null tag"); } cancelAll(new RequestFilter() { @Override public boolean apply(Request request) { return request.getTag() == tag; } }); } /** * Adds a Request to the dispatch queue. * * @param request The request to service * @return The passed-in request */ public , T> R add(R request) { //Listener factories should be first so client can listen to events for (ListenerFactory listenerFactory : listenerFactories) { RequestListener.ResponseListener qResponseListener = listenerFactory .getResponseListener(request); RequestListener.ErrorListener qErrorListener = listenerFactory.getErrorListener (request); RequestListener.MarkerListener qMarkerListener = listenerFactory.getMarkerListener (request); if (qMarkerListener != null) { request.addMarkerListener(qMarkerListener); } if (qResponseListener != null) { request.addResponseListener(qResponseListener); } if (qErrorListener != null) { request.addErrorListener(qErrorListener); } } if (JusLog.ErrorLog.isOn()) { request.addErrorListener(JusLog.ErrorLog.getLogger(request)); } if (JusLog.ResponseLog.isOn()) { request.addResponseListener(JusLog.ResponseLog.getLogger(request)); } if (JusLog.MarkerLog.isOn()) { request.addMarkerListener(JusLog.MarkerLog.getLogger(request)); } request.addMarker(Request.EVENT_PRE_ADD_TO_QUEUE); Authenticator serverAuthenticator = null; Authenticator proxyAuthenticator = null; for (Authenticator.Factory factory : authenticatorFactories) { if (serverAuthenticator == null) { serverAuthenticator = factory.forServer(request.getUrl(), request .getNetworkRequest()); if (serverAuthenticator != null) request.setServerAuthenticator(serverAuthenticator); } if (proxyAuthenticator == null) { proxyAuthenticator = factory.forProxy(request.getUrl(), request.getNetworkRequest ()); if (proxyAuthenticator != null) request.setProxyAuthenticator(proxyAuthenticator); } if (proxyAuthenticator != null && serverAuthenticator != null) { break; } } for (Transformer.RequestTransformer transformer : requestTransformers) { if (transformer.filter == null || transformer.filter.apply(request)) { request.setNetworkRequest(transformer.transform(request.getNetworkRequest())); } } if (retryPolicyFactory != null) { request.setRetryPolicy(retryPolicyFactory.get(request)); } if (redirectPolicyFactory != null) { request.setRedirectPolicy(redirectPolicyFactory.get(request)); } if (connectivityManagerFactory != null) { request.setConnectivityManager(connectivityManagerFactory.get(request)); } if (noConnectionPolicyFactory != null) { request.setNoConnectionPolicy(noConnectionPolicyFactory.get(request)); } synchronized (currentRequests) { //check if not already cancelled if (request.isCanceled()) { request.finish(Request.EVENT_ADD_DISCARD_CANCELED); } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); // Tag the request as belonging to this queue and add it to the set of current requests. request.setRequestQueue(this); currentRequests.add(request); } request.addMarker(Request.EVENT_ADD_TO_QUEUE); // If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { networkQueue.add(request); return request; } // Insert request into stage if there's already a request with the same cache key in flight. synchronized (waitingRequests) { String cacheKey = request.getCacheKey(); if (waitingRequests.containsKey(cacheKey)) { // There is already a request in flight. Queue up. Queue> stagedRequests = waitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList<>(); } stagedRequests.add(request); waitingRequests.put(cacheKey, stagedRequests); // if (JusLog.DEBUG) { // JusLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); // } //todo add queue markers } else { // Insert 'empty list' queue for this cacheKey, indicating there is now a request in // flight. waitingRequests.put(cacheKey, new LinkedList>()); cacheQueue.add(request); } return request; } } public final NetworkResponse transformResponse(Request request, NetworkResponse response) { NetworkResponse currResponse = response; for (Transformer.ResponseTransformer transformer : responseTransformers) { if (transformer.filter == null || transformer.filter.apply(request)) { currResponse = transformer.transform(currResponse); } } return currResponse; } /** * Called from {@link Request#finish(String)}, indicating that processing of the given request * has finished. *

*

Releases waiting requests for request.getCacheKey() if * request.shouldCache().

*/ void finish(Request request) { // Remove from the set of requests currently being processed. synchronized (currentRequests) { currentRequests.remove(request); } if (request.shouldCache()) { synchronized (waitingRequests) { String cacheKey = request.getCacheKey(); Queue> waitingRequests = this.waitingRequests.remove(cacheKey); if (waitingRequests != null) { //todo add queue markers // if (JusLog.DEBUG) { // JusLog.v("Releasing %d waiting requests for cacheKey=%s.", // waitingRequests.size(), cacheKey); // } // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. cacheQueue.addAll(waitingRequests); } } } } public RetryPolicy.Factory getRetryPolicyFactory() { return retryPolicyFactory; } public RequestQueue setRetryPolicyFactory(RetryPolicy.Factory retryPolicyFactory) { this.retryPolicyFactory = retryPolicyFactory; return this; } public RedirectPolicy.Factory getRedirectPolicyFactory() { return redirectPolicyFactory; } public RequestQueue setRedirectPolicyFactory(RedirectPolicy.Factory redirectPolicyFactory) { this.redirectPolicyFactory = redirectPolicyFactory; return this; } public ConnectivityManager.Factory getConnectivityManagerFactory() { return connectivityManagerFactory; } public RequestQueue setConnectivityManagerFactory(ConnectivityManager.Factory connectivityManagerFactory) { this.connectivityManagerFactory = connectivityManagerFactory; return this; } public NoConnectionPolicy.Factory getNoConnectionPolicyFactory() { return noConnectionPolicyFactory; } public RequestQueue setNoConnectionPolicyFactory(NoConnectionPolicy.Factory noConnectionPolicyFactory) { this.noConnectionPolicyFactory = noConnectionPolicyFactory; return this; } public RequestQueue addAuthenticatorFactory(Authenticator.Factory factory) { synchronized (authenticatorFactories) { authenticatorFactories.add(factory); } return this; } public RequestQueue removeAuthenticatorFactory(Authenticator.Factory factory) { synchronized (authenticatorFactories) { authenticatorFactories.remove(factory); } return this; } public RequestQueue addConverterFactory(Factory factory) { synchronized (converterFactories) { converterFactories.add(factory); } return this; } public RequestQueue removeConverterFactory(Factory factory) { synchronized (converterFactories) { converterFactories.remove(factory); } return this; } public RequestQueue addRequestTransformer(Transformer.RequestTransformer transformer) { synchronized (requestTransformers) { requestTransformers.add(transformer); } return this; } public RequestQueue removeRequestTransformer(Transformer.RequestTransformer transformer) { synchronized (requestTransformers) { requestTransformers.remove(transformer); } return this; } public RequestQueue addResponseTransformer(Transformer.ResponseTransformer transformer) { synchronized (responseTransformers) { responseTransformers.add(transformer); } return this; } public RequestQueue removeResponseTransformer(Transformer.ResponseTransformer transformer) { synchronized (responseTransformers) { responseTransformers.remove(transformer); } return this; } public RequestQueue addListenerFactory(RequestListener.ListenerFactory listenerFactory) { Utils.checkNotNull(listenerFactory, "listenerFactory==null"); synchronized (listenerFactories) { listenerFactories.add(listenerFactory); } return this; } public RequestQueue removeListenerFactory(RequestListener.ListenerFactory listenerFactory) { synchronized (listenerFactories) { listenerFactories.remove(listenerFactory); } return this; } /** * Returns a {@link Converter} for {@link io.apptik.comm.jus.NetworkResponse} to {@code type} * from the available * {@linkplain #converterFactories factories}. */ public Converter getResponseConverter(Type type, Annotation[] annotations) { checkNotNull(type, "type == null"); checkNotNull(annotations, "annotations == null"); for (int i = 0, count = converterFactories.size(); i < count; i++) { Converter converter = converterFactories.get(i).fromResponse(type, annotations); if (converter != null) { return converter; } } StringBuilder builder = new StringBuilder("Could not locate Response converter for ") .append(type) .append(". Tried:"); for (Factory converterFactory : converterFactories) { builder.append("\n * ").append(converterFactory.getClass().getName()); } throw new IllegalArgumentException(builder.toString()); } public int getCurrentRequests() { synchronized (currentRequests) { return currentRequests.size(); } } public int getWaitingRequests() { synchronized (waitingRequests) { return waitingRequests.size(); } } //// /** * A simple predicate or filter interface for Requests, for use by * {@link RequestQueue#cancelAll(RequestFilter)}. */ public interface RequestFilter { boolean apply(Request request); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy