/*
* Created by Angel Leon (@gubatron), Alden Torres (aldenml)
* Copyright (c) 2011-2014, FrostWire(R). All rights reserved.
* 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 com.frostwire.search;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.frostwire.concurrent.DefaultThreadFactory;
/**
*
* @author gubatron
* @author aldenml
*
*/
public class SearchManagerImpl implements SearchManager {
private static final Logger LOG = LoggerFactory.getLogger(SearchManagerImpl.class);
private static final int DEFAULT_NTHREADS = 4;
private final ExecutorService executor;
private final List<SearchTask> tasks;
private SearchManagerListener listener;
public SearchManagerImpl(int nThreads) {
this.executor = newFixedThreadPool(nThreads);
this.tasks = Collections.synchronizedList(new LinkedList<SearchTask>());
}
public SearchManagerImpl() {
this(DEFAULT_NTHREADS);
}
@Override
public void registerListener(SearchManagerListener listener) {
this.listener = listener;
}
@Override
public void perform(SearchPerformer performer) {
if (performer != null) {
if (performer.getToken() < 0) {
throw new IllegalArgumentException("Search token id mut be >= 0");
}
performer.registerListener(new PerformerResultListener(this));
SearchTask task = new PerformTask(this, performer, getOrder(performer.getToken()));
submitSearchTask(task);
} else {
LOG.warn("Search performer is null, review your logic");
}
}
public void submitSearchTask(SearchTask task) {
tasks.add(task);
executor.execute(task);
}
@Override
public void stop() {
stopTasks(-1L);
}
@Override
public void stop(long token) {
stopTasks(token);
}
@Override
public boolean shutdown(long timeout, TimeUnit unit) {
stop();
executor.shutdown();
try {
if (!executor.awaitTermination(timeout, unit)) {
executor.shutdownNow();
// wait a while for tasks to respond to being cancelled
if (!executor.awaitTermination(timeout, unit)) {
LOG.error("Pool did not terminate");
return false;
}
}
} catch (InterruptedException ie) {
// (re-)cancel if current thread also interrupted
executor.shutdownNow();
// preserve interrupt status
Thread.currentThread().interrupt();
}
return tasks.isEmpty();
}
protected void onResults(SearchPerformer performer, List<? extends SearchResult> results) {
try {
if (listener != null) {
listener.onResults(performer, results);
}
} catch (Throwable e) {
LOG.warn("Error sending results back to receiver: " + e.getMessage());
}
}
protected void onFinished(long token) {
try {
if (listener != null) {
listener.onFinished(token);
}
} catch (Throwable e) {
LOG.warn("Error sending results back to receiver: " + e.getMessage());
}
}
private void stopTasks(long token) {
synchronized (tasks) {
Iterator<SearchTask> it = tasks.iterator();
while (it.hasNext()) {
SearchTask task = it.next();
if (token == -1L || task.getToken() == token) {
task.stop();
}
}
}
}
public void crawl(SearchPerformer performer, CrawlableSearchResult sr) {
if (performer != null && !performer.isStopped()) {
try {
SearchTask task = new CrawlTask(this, performer, sr, getOrder(performer.getToken()));
submitSearchTask(task);
} catch (Throwable e) {
LOG.warn("Error scheduling crawling of search result: " + sr);
}
} else {
LOG.warn("Search performer is null or stopped, review your logic");
}
}
void checkIfFinished(SearchPerformer performer) {
SearchTask pendingTask = null;
synchronized (tasks) {
Iterator<SearchTask> it = tasks.iterator();
while (it.hasNext() && pendingTask == null) {
SearchTask task = it.next();
if (task.getToken() == performer.getToken() && !task.isStopped()) {
pendingTask = task;
}
if (task.isStopped()) {
it.remove();
}
}
}
if (pendingTask == null) {
onFinished(performer.getToken());
}
}
private int getOrder(long token) {
int order = 0;
synchronized (tasks) {
Iterator<SearchTask> it = tasks.iterator();
while (it.hasNext()) {
SearchTask task = it.next();
if (task.getToken() == token) {
order = order + 1;
}
}
}
return order;
}
private static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>(), new DefaultThreadFactory("SearchManager", false));
}
private static abstract class SearchTask implements Runnable, Comparable<SearchTask> {
protected final SearchManagerImpl manager;
protected final SearchPerformer performer;
private final int order;
public SearchTask(SearchManagerImpl manager, SearchPerformer performer, int order) {
this.manager = manager;
this.performer = performer;
this.order = order;
}
public long getToken() {
return performer.getToken();
}
public boolean isStopped() {
return performer.isStopped();
}
public void stop() {
performer.stop();
}
@Override
public int compareTo(SearchTask o) {
return order - o.order;
}
}
private static final class PerformTask extends SearchTask {
public PerformTask(SearchManagerImpl manager, SearchPerformer performer, int order) {
super(manager, performer, order);
}
@Override
public void run() {
try {
if (!isStopped()) {
performer.perform();
}
} catch (Throwable e) {
LOG.warn("Error performing search: " + performer + ", e=" + e.getMessage());
} finally {
if (manager.tasks.remove(this)) {
manager.checkIfFinished(performer);
}
}
}
}
private static final class CrawlTask extends SearchTask {
private final CrawlableSearchResult sr;
public CrawlTask(SearchManagerImpl manager, SearchPerformer performer, CrawlableSearchResult sr, int order) {
super(manager, performer, order);
this.sr = sr;
}
@Override
public void run() {
try {
if (!isStopped()) {
performer.crawl(sr);
}
} catch (Throwable e) {
LOG.warn("Error performing crawling of: " + sr + ", e=" + e.getMessage());
} finally {
if (manager.tasks.remove(this)) {
manager.checkIfFinished(performer);
}
}
}
}
}