package org.infinispan.remoting.transport; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import org.infinispan.xsite.XSiteBackup; import org.infinispan.xsite.XSiteReplicateCommand; /** * Invokes a command in a remote site. This class allows to set a {@link org.infinispan.remoting.transport.RetryOnFailureXSiteCommand.RetryPolicy} * to retry the command in case of an exception. The {@link org.infinispan.remoting.transport.RetryOnFailureXSiteCommand.RetryPolicy} * has the exception to decide if it should retry or not. * * @author Pedro Ruivo * @since 7.0 */ public class RetryOnFailureXSiteCommand { public static final RetryPolicy NO_RETRY = new MaxRetriesPolicy(0); private static final Log log = LogFactory.getLog(RetryOnFailureXSiteCommand.class); private static final boolean trace = log.isTraceEnabled(); private static final boolean debug = log.isDebugEnabled(); private final Collection<XSiteBackup> backups; private final XSiteReplicateCommand command; private final RetryPolicy retryPolicy; private RetryOnFailureXSiteCommand(Collection<XSiteBackup> backups, XSiteReplicateCommand command, RetryPolicy retryPolicy) { this.backups = backups; this.command = command; this.retryPolicy = retryPolicy; } /** * Invokes remotely the command using the {@code Transport} passed as parameter. * * @param transport the {@link org.infinispan.remoting.transport.Transport} to use. * @param waitTimeBetweenRetries the waiting time if the command fails before retrying it. * @param unit the {@link java.util.concurrent.TimeUnit} of the waiting time. * @throws Throwable if the maximum retries is reached (defined by the {@link org.infinispan.remoting.transport.RetryOnFailureXSiteCommand.RetryPolicy}, * the last exception occurred is thrown. */ public void execute(Transport transport, long waitTimeBetweenRetries, TimeUnit unit) throws Throwable { if (backups.isEmpty()) { if (debug) { log.debugf("Executing '%s' but backup list is empty.", this); } return; } assertNotNull(transport, "Transport"); assertNotNull(unit, "TimeUnit"); assertGreaterThanZero(waitTimeBetweenRetries, "WaitTimeBetweenRetries"); do { if (trace) { log.tracef("Sending %s to %s", command, backups); } BackupResponse response = transport.backupRemotely(backups, command); response.waitForBackupToFinish(); Throwable throwable = extractThrowable(response); if (throwable == null) { if (trace) { log.trace("Successfull Response received."); } return; } else if (!retryPolicy.retry(throwable, transport)) { if (trace) { log.tracef("Exception Response received. Exception is %s", throwable); } throw throwable; } unit.sleep(waitTimeBetweenRetries); } while (true); } /** * It builds a new instance with the destination site, the command and the retry policy. * * @param backup the destination site. * @param command the command to invoke remotely. * @param retryPolicy the retry policy. * @return the new instance. * @throws java.lang.NullPointerException if any parameter is {@code null} */ public static RetryOnFailureXSiteCommand newInstance(XSiteBackup backup, XSiteReplicateCommand command, RetryPolicy retryPolicy) { assertNotNull(backup, "XSiteBackup"); assertNotNull(command, "XSiteReplicateCommand"); assertNotNull(retryPolicy, "RetryPolicy"); return new RetryOnFailureXSiteCommand(Collections.singletonList(backup), command, retryPolicy); } @Override public String toString() { return "RetryOnLinkFailureXSiteCommand{" + "backups=" + backups + ", command=" + command + '}'; } private static void assertNotNull(Object value, String field) { if (value == null) { throw new NullPointerException(field + " must be not null."); } } private static void assertGreaterThanZero(long value, String field) { if (value <= 0) { throw new IllegalArgumentException(field + " must be greater that zero but instead it is " + value); } } private static Throwable extractThrowable(BackupResponse response) { Map<?, Throwable> errorMap = response.getFailedBackups(); //noinspection ThrowableResultOfMethodCallIgnored return errorMap.isEmpty() ? null : errorMap.values().iterator().next(); } public static interface RetryPolicy { boolean retry(Throwable throwable, Transport transport); } public static class MaxRetriesPolicy implements RetryPolicy { private int maxRetries; public MaxRetriesPolicy(int maxRetries) { this.maxRetries = maxRetries; } @Override public boolean retry(Throwable throwable, Transport transport) { return maxRetries-- > 0; } } }