diff --git a/src/main/java/org/acra/annotation/ReportsCrashes.java b/src/main/java/org/acra/annotation/ReportsCrashes.java index 87bd41458..bb6d1eea0 100644 --- a/src/main/java/org/acra/annotation/ReportsCrashes.java +++ b/src/main/java/org/acra/annotation/ReportsCrashes.java @@ -30,6 +30,8 @@ import org.acra.ReportingInteractionMode; import org.acra.builder.NoOpReportPrimer; import org.acra.builder.ReportPrimer; +import org.acra.config.DefaultRetryPolicy; +import org.acra.config.RetryPolicy; import org.acra.dialog.BaseCrashReportDialog; import org.acra.dialog.CrashReportDialog; import org.acra.security.KeyStoreFactory; @@ -592,4 +594,10 @@ * @return specify the type of the certificate set in either {@link #certificatePath()} or {@link #resCertificate()} */ @NonNull String certificateType() default ACRAConstants.DEFAULT_CERTIFICATE_TYPE; + + + /** + * @return a Class that decides if a report should be resent (usually if one or more senders failed). + */ + @NonNull Class retryPolicyClass() default DefaultRetryPolicy.class; } diff --git a/src/main/java/org/acra/config/ACRAConfiguration.java b/src/main/java/org/acra/config/ACRAConfiguration.java index 432d5db6d..80c2e983c 100644 --- a/src/main/java/org/acra/config/ACRAConfiguration.java +++ b/src/main/java/org/acra/config/ACRAConfiguration.java @@ -111,6 +111,7 @@ public final class ACRAConfiguration implements Serializable { private final int resCertificate; private final String certificatePath; private final String certificateType; + private final Class retryPolicyClass; /** * @param builder ConfigurationBuilder with which to initialise this {@link ACRAConfiguration}. @@ -165,6 +166,7 @@ public final class ACRAConfiguration implements Serializable { resCertificate = builder.resCertificate(); certificatePath = builder.certificatePath(); certificateType = builder.certificateType(); + retryPolicyClass = builder.retryPolicyClass(); } /** @@ -407,11 +409,18 @@ public int resCertificate() { return resCertificate; } + @NonNull public String certificatePath() { return certificatePath; } + @NonNull public String certificateType() { return certificateType; } + + @NonNull + public Class retryPolicyClass() { + return retryPolicyClass; + } } diff --git a/src/main/java/org/acra/config/ConfigurationBuilder.java b/src/main/java/org/acra/config/ConfigurationBuilder.java index c1816dc3a..bd0f1ba6c 100644 --- a/src/main/java/org/acra/config/ConfigurationBuilder.java +++ b/src/main/java/org/acra/config/ConfigurationBuilder.java @@ -117,6 +117,7 @@ public final class ConfigurationBuilder { @RawRes private Integer resCertificate; private String certificatePath; private String certificateType; + private Class retryPolicyClass; /** @@ -181,6 +182,7 @@ public ConfigurationBuilder(@NonNull Application app) { resCertificate = annotationConfig.resCertificate(); certificatePath = annotationConfig.certificatePath(); certificateType = annotationConfig.certificateType(); + retryPolicyClass = annotationConfig.retryPolicyClass(); } else { annotationType = null; } @@ -794,6 +796,12 @@ public ConfigurationBuilder setReportPrimerClass(@NonNull Class retryPolicyClass) { + this.retryPolicyClass = retryPolicyClass; + return this; + } + // Getters - used to provide values and !DEFAULTS! to ACRConfiguration during construction @@ -1205,4 +1213,12 @@ String certificateType() { Map httpHeaders() { return httpHeaders; } + + @NonNull + Class retryPolicyClass(){ + if(retryPolicyClass != null){ + return retryPolicyClass; + } + return DefaultRetryPolicy.class; + } } diff --git a/src/main/java/org/acra/config/DefaultRetryPolicy.java b/src/main/java/org/acra/config/DefaultRetryPolicy.java new file mode 100644 index 000000000..c5a1e9811 --- /dev/null +++ b/src/main/java/org/acra/config/DefaultRetryPolicy.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 + * + * 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 org.acra.config; + +import org.acra.sender.ReportSender; + +import java.util.List; + +/** + * Default {@link RetryPolicy}. Only resend if all senders failed. + * @author F43nd1r + * @since 4.9.0 + */ +public class DefaultRetryPolicy implements RetryPolicy { + @Override + public boolean shouldRetrySend(List senders, List failedSenders) { + return senders.size() == failedSenders.size(); + } +} diff --git a/src/main/java/org/acra/config/RetryPolicy.java b/src/main/java/org/acra/config/RetryPolicy.java new file mode 100644 index 000000000..b8584fe19 --- /dev/null +++ b/src/main/java/org/acra/config/RetryPolicy.java @@ -0,0 +1,55 @@ +/* + * Copyright 2016 + * + * 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 org.acra.config; + +import org.acra.sender.ReportSender; +import org.acra.sender.ReportSenderException; + +import java.util.List; + +/** + * A policy which determines if a report should be resent. + * + * @author F43nd1r + * @since 4.9.0 + */ +public interface RetryPolicy { + + /** + * @param senders a list of all senders + * @param failedSenders a list of all failed senders with the thrown exceptions + * @return if the request should be resent later + */ + boolean shouldRetrySend(List senders, List failedSenders); + + class FailedSender { + private final ReportSender sender; + private final ReportSenderException exception; + + public FailedSender(ReportSender sender, ReportSenderException exception) { + this.sender = sender; + this.exception = exception; + } + + public ReportSender getSender() { + return sender; + } + + public ReportSenderException getException() { + return exception; + } + } +} diff --git a/src/main/java/org/acra/sender/ReportDistributor.java b/src/main/java/org/acra/sender/ReportDistributor.java index 14b0e19ef..71dc4477e 100644 --- a/src/main/java/org/acra/sender/ReportDistributor.java +++ b/src/main/java/org/acra/sender/ReportDistributor.java @@ -23,10 +23,14 @@ import org.acra.ACRA; import org.acra.collector.CrashReportData; import org.acra.config.ACRAConfiguration; +import org.acra.config.DefaultRetryPolicy; +import org.acra.config.RetryPolicy; import org.acra.file.CrashReportPersister; import java.io.File; import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import static org.acra.ACRA.LOG_TAG; @@ -93,33 +97,43 @@ public void distribute(@NonNull File reportFile) { */ private void sendCrashReport(@NonNull CrashReportData errorContent) throws ReportSenderException { if (!isDebuggable() || config.sendReportsInDevMode()) { - boolean sentAtLeastOnce = false; - ReportSenderException sendFailure = null; - String failedSender = null; + List failedSenders = new LinkedList(); for (ReportSender sender : reportSenders) { try { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Sending report using " + sender.getClass().getName()); sender.send(context, errorContent); if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Sent report using " + sender.getClass().getName()); - // If at least one sender worked, don't re-send the report later. - sentAtLeastOnce = true; } catch (ReportSenderException e) { - sendFailure = e; - failedSender = sender.getClass().getName(); + failedSenders.add(new RetryPolicy.FailedSender(sender, e)); } } - if (sendFailure != null) { - // We had some failure - if (!sentAtLeastOnce) { - throw sendFailure; // Don't log here because we aren't dealing with the Exception here. - } else { - ACRA.log.w(LOG_TAG, - "ReportSender of class " - + failedSender - + " failed but other senders completed their task. ACRA will not send this report again."); + RetryPolicy policy = null; + try { + policy = config.retryPolicyClass().newInstance(); + } catch (InstantiationException e) { + ACRA.log.e(LOG_TAG, "Failed to create policy instance of class " + config.retryPolicyClass().getName(), e); + } catch (IllegalAccessException e) { + ACRA.log.e(LOG_TAG, "Failed to create policy instance of class " + config.retryPolicyClass().getName(), e); + } + if(policy == null){ + policy = new DefaultRetryPolicy(); + } + + if (policy.shouldRetrySend(reportSenders, failedSenders)) { + ReportSenderException exception = new ReportSenderException("Policy marked this task as incomplete. ACRA will try to send this report again."); + if(failedSenders.size() > 0) exception.initCause(failedSenders.get(failedSenders.size() - 1).getException()); + throw exception; + } else { + StringBuilder builder = new StringBuilder("ReportSenders of classes ["); + for (Iterator iterator = failedSenders.iterator(); iterator.hasNext();){ + RetryPolicy.FailedSender failedSender = iterator.next(); + builder.append(failedSender.getSender().getClass().getName()); + if(iterator.hasNext()) builder.append(", "); } + builder.append("] failed, but Policy marked this task as complete. ACRA will not send this report again."); + ACRA.log.w(LOG_TAG, builder.toString()); } } }