diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/Job.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/Job.java index d4793a0355..b1d525d845 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/Job.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/Job.java @@ -21,6 +21,10 @@ import org.whispersystems.jobqueue.requirements.Requirement; import java.io.Serializable; import java.util.List; +/** + * An abstract class representing a unit of work that can be scheduled with + * the JobManager. This should be extended to implement tasks. + */ public abstract class Job implements Serializable { private final JobParameters parameters; @@ -80,9 +84,31 @@ public abstract class Job implements Serializable { this.runIteration = runIteration; } + /** + * Called after a job has been added to the JobManager queue. If it's a persistent job, + * the state has been persisted to disk before this method is called. + */ public abstract void onAdded(); + + /** + * Called to actually execute the job. + * @throws Exception + */ public abstract void onRun() throws Exception; + + /** + * If onRun() throws an exception, this method will be called to determine whether the + * job should be retried. + * + * @param exception The exception onRun() threw. + * @return true if onRun() should be called again, false otherwise. + */ public abstract boolean onShouldRetry(Exception exception); + + /** + * Called if a job fails to run (onShouldRetry returned false, or the number of retries exceeded + * the job's configured retry count. + */ public abstract void onCanceled(); diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java index d1febb53a3..194b866fce 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobConsumer.java @@ -20,7 +20,7 @@ import android.util.Log; import org.whispersystems.jobqueue.persistence.PersistentStorage; -public class JobConsumer extends Thread { +class JobConsumer extends Thread { private static final String TAG = JobConsumer.class.getSimpleName(); diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java index 641e68984b..71b3055f2c 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobManager.java @@ -27,11 +27,17 @@ import org.whispersystems.jobqueue.requirements.RequirementProvider; import java.io.IOException; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; +/** + * A JobManager allows you to enqueue {@link org.whispersystems.jobqueue.Job} tasks + * that are executed once a Job's {@link org.whispersystems.jobqueue.requirements.Requirement}s + * are met. + */ public class JobManager implements RequirementListener { private final JobQueue jobQueue = new JobQueue(); @@ -64,15 +70,22 @@ public class JobManager implements RequirementListener { } } + /** + * @param context An Android {@link android.content.Context}. + * @return a {@link org.whispersystems.jobqueue.JobManager.Builder} used to construct a JobManager. + */ public static Builder newBuilder(Context context) { return new Builder(context); } + /** + * Returns a {@link org.whispersystems.jobqueue.requirements.RequirementProvider} registered with + * the JobManager by name. + * + * @param name The name of the registered {@link org.whispersystems.jobqueue.requirements.RequirementProvider} + * @return The RequirementProvider, or null if no provider is registered with that name. + */ public RequirementProvider getRequirementProvider(String name) { - if (requirementProviders == null || requirementProviders.isEmpty()) { - return null; - } - for (RequirementProvider provider : requirementProviders) { if (provider.getName().equals(name)) { return provider; @@ -88,6 +101,11 @@ public class JobManager implements RequirementListener { } } + /** + * Queue a {@link org.whispersystems.jobqueue.Job} to be executed. + * + * @param job The Job to be executed. + */ public void add(final Job job) { eventExecutor.execute(new Runnable() { @Override @@ -153,36 +171,79 @@ public class JobManager implements RequirementListener { this.consumerThreads = 5; } + /** + * A name for the {@link org.whispersystems.jobqueue.JobManager}. This is a required parameter, + * and is linked to the durable queue used by persistent jobs. + * + * @param name The name for the JobManager to build. + * @return The builder. + */ public Builder withName(String name) { this.name = name; return this; } + /** + * The {@link org.whispersystems.jobqueue.requirements.RequirementProvider}s to register with this + * JobManager. Optional. Each {@link org.whispersystems.jobqueue.requirements.Requirement} an + * enqueued Job depends on should have a matching RequirementProvider registered here. + * + * @param requirementProviders The RequirementProviders + * @return The builder. + */ public Builder withRequirementProviders(RequirementProvider... requirementProviders) { this.requirementProviders = Arrays.asList(requirementProviders); return this; } + /** + * The {@link org.whispersystems.jobqueue.dependencies.DependencyInjector} to use for injecting + * dependencies into {@link Job}s. Optional. Injection occurs just before a Job's onAdded() callback, or + * after deserializing a persistent job. + * + * @param dependencyInjector The injector to use. + * @return The builder. + */ public Builder withDependencyInjector(DependencyInjector dependencyInjector) { this.dependencyInjector = dependencyInjector; return this; } + /** + * The {@link org.whispersystems.jobqueue.persistence.JobSerializer} to use for persistent Jobs. + * Required if persistent Jobs are used. + * + * @param jobSerializer The serializer to use. + * @return The builder. + */ public Builder withJobSerializer(JobSerializer jobSerializer) { this.jobSerializer = jobSerializer; return this; } + /** + * Set the number of threads dedicated to consuming Jobs from the queue and executing them. + * + * @param consumerThreads The number of threads. + * @return The builder. + */ public Builder withConsumerThreads(int consumerThreads) { this.consumerThreads = consumerThreads; return this; } + /** + * @return A constructed JobManager. + */ public JobManager build() { if (name == null) { throw new IllegalArgumentException("You must specify a name!"); } + if (requirementProviders == null) { + requirementProviders = new LinkedList<>(); + } + return new JobManager(context, name, requirementProviders, dependencyInjector, jobSerializer, consumerThreads); diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobParameters.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobParameters.java index e42672371a..ff6951779e 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobParameters.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobParameters.java @@ -22,6 +22,9 @@ import java.io.Serializable; import java.util.LinkedList; import java.util.List; +/** + * The set of parameters that describe a {@link org.whispersystems.jobqueue.Job}. + */ public class JobParameters implements Serializable { private transient EncryptionKeys encryptionKeys; @@ -63,6 +66,9 @@ public class JobParameters implements Serializable { return retryCount; } + /** + * @return a builder used to construct JobParameters. + */ public static Builder newBuilder() { return new Builder(); } @@ -78,31 +84,64 @@ public class JobParameters implements Serializable { private int retryCount = 100; private String groupId = null; + /** + * Specify a {@link org.whispersystems.jobqueue.requirements.Requirement }that must be met + * before the Job is executed. May be called multiple times to register multiple requirements. + * @param requirement The Requirement that must be met. + * @return the builder. + */ public Builder withRequirement(Requirement requirement) { this.requirements.add(requirement); return this; } + /** + * Specify that the Job should be durably persisted to disk, so that it remains in the queue + * across application restarts. + * @return The builder. + */ public Builder withPersistence() { this.isPersistent = true; return this; } + /** + * Specify that the job should use encryption when durably persisted to disk. + * @param encryptionKeys The keys to encrypt the serialized job with before persisting. + * @return the builder. + */ public Builder withEncryption(EncryptionKeys encryptionKeys) { this.encryptionKeys = encryptionKeys; return this; } + /** + * Specify how many times the job should be retried if execution fails but onShouldRetry() returns + * true. + * + * @param retryCount The number of times the job should be retried. + * @return the builder. + */ public Builder withRetryCount(int retryCount) { this.retryCount = retryCount; return this; } + /** + * Specify a groupId the job should belong to. Jobs with the same groupId are guaranteed to be + * executed serially. + * + * @param groupId The job's groupId. + * @return the builder. + */ public Builder withGroupId(String groupId) { this.groupId = groupId; return this; } + /** + * @return the JobParameters instance that describes a Job. + */ public JobParameters create() { return new JobParameters(requirements, isPersistent, groupId, encryptionKeys, retryCount); } diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobQueue.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobQueue.java index e8c031fc46..7d098c0c4e 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/JobQueue.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/JobQueue.java @@ -16,29 +16,27 @@ */ package org.whispersystems.jobqueue; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.Set; -public class JobQueue { +class JobQueue { private final Set activeGroupIds = new HashSet<>(); private final LinkedList jobQueue = new LinkedList<>(); - public synchronized void onRequirementStatusChanged() { + synchronized void onRequirementStatusChanged() { notifyAll(); } - public synchronized void add(Job job) { + synchronized void add(Job job) { jobQueue.add(job); notifyAll(); } - public synchronized void addAll(List jobs) { + synchronized void addAll(List jobs) { jobQueue.addAll(jobs); notifyAll(); } @@ -47,7 +45,7 @@ public class JobQueue { jobQueue.push(job); } - public synchronized Job getNext() { + synchronized Job getNext() { try { Job nextAvailableJob; @@ -61,7 +59,7 @@ public class JobQueue { } } - public synchronized void setGroupIdAvailable(String groupId) { + synchronized void setGroupIdAvailable(String groupId) { if (groupId != null) { activeGroupIds.remove(groupId); notifyAll(); diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/dependencies/DependencyInjector.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/dependencies/DependencyInjector.java index e0234ef47b..cb396bed14 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/dependencies/DependencyInjector.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/dependencies/DependencyInjector.java @@ -1,5 +1,24 @@ +/** + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 . + */ package org.whispersystems.jobqueue.dependencies; +/** + * Interface responsible for injecting dependencies into Jobs. + */ public interface DependencyInjector { public void injectDependencies(Object object); } diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JavaJobSerializer.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JavaJobSerializer.java index 719f51fd07..1933ce10b8 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JavaJobSerializer.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JavaJobSerializer.java @@ -30,6 +30,10 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +/** + * An implementation of {@link org.whispersystems.jobqueue.persistence.JobSerializer} that uses + * Java Serialization. + */ public class JavaJobSerializer implements JobSerializer { private final Context context; diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JobSerializer.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JobSerializer.java index 226c9b6494..6c0384e655 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JobSerializer.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/persistence/JobSerializer.java @@ -21,9 +21,27 @@ import org.whispersystems.jobqueue.Job; import java.io.IOException; +/** + * A JobSerializer is responsible for serializing and deserializing persistent jobs. + */ public interface JobSerializer { + /** + * Serialize a job object into a string. + * @param job The Job to serialize. + * @return The serialized Job. + * @throws IOException if serialization fails. + */ public String serialize(Job job) throws IOException; + + /** + * Deserialize a String into a Job. + * @param keys Optional encryption keys that could have been used. + * @param encrypted True if the job was encrypted using the encryption keys. + * @param serialized The serialized Job. + * @return The deserialized Job. + * @throws IOException If the Job deserialization fails. + */ public Job deserialize(EncryptionKeys keys, boolean encrypted, String serialized) throws IOException; } diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/NetworkRequirement.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/NetworkRequirement.java index 924e9cca6e..315a6c6782 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/NetworkRequirement.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/NetworkRequirement.java @@ -22,6 +22,9 @@ import android.net.NetworkInfo; import org.whispersystems.jobqueue.dependencies.ContextDependent; +/** + * A requirement that is satisfied when a network connection is present. + */ public class NetworkRequirement implements Requirement, ContextDependent { private transient Context context; diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/Requirement.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/Requirement.java index 058497e495..27a9aa4936 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/Requirement.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/Requirement.java @@ -18,6 +18,12 @@ package org.whispersystems.jobqueue.requirements; import java.io.Serializable; +/** + * A Requirement that must be satisfied before a Job can run. + */ public interface Requirement extends Serializable { + /** + * @return true if the requirement is satisfied, false otherwise. + */ public boolean isPresent(); } diff --git a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/RequirementProvider.java b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/RequirementProvider.java index cb3dc281b4..5091ec16d0 100644 --- a/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/RequirementProvider.java +++ b/jobqueue/src/main/java/org/whispersystems/jobqueue/requirements/RequirementProvider.java @@ -16,7 +16,22 @@ */ package org.whispersystems.jobqueue.requirements; +/** + * Notifies listeners when a {@link org.whispersystems.jobqueue.requirements.Requirement}'s + * state is likely to have changed. + */ public interface RequirementProvider { + /** + * @return The name of the provider. + */ public String getName(); + + /** + * The {@link org.whispersystems.jobqueue.requirements.RequirementListener} to call when + * a {@link org.whispersystems.jobqueue.requirements.Requirement}'s status is likely to + * have changed. + * + * @param listener The listener to call. + */ public void setListener(RequirementListener listener); }