![JAR search and dependency download from the Maven repository](/logo.png)
com.oracle.tools.deferred.DeferredHelper Maven / Gradle / Ivy
Show all versions of oracle-tools-all Show documentation
/*
* File: DeferredHelper.java
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* The contents of this file are subject to the terms and conditions of
* the Common Development and Distribution License 1.0 (the "License").
*
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the License by consulting the LICENSE.txt file
* distributed with this file, or by consulting https://oss.oracle.com/licenses/CDDL
*
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file LICENSE.txt.
*
* MODIFICATIONS:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*/
package com.oracle.tools.deferred;
import com.oracle.tools.deferred.atomic.DeferredAtomicBoolean;
import com.oracle.tools.deferred.atomic.DeferredAtomicInteger;
import com.oracle.tools.deferred.atomic.DeferredAtomicLong;
import com.oracle.tools.util.*;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* The {@link DeferredHelper} defines a collection of static helper methods
* for working with {@link Deferred}s.
*
* Copyright (c) 2012. All Rights Reserved. Oracle Corporation.
* Oracle is a registered trademark of Oracle Corporation and/or its affiliates.
*
* @author Brian Oliver
*/
public class DeferredHelper
{
/**
* The retry strategy that will be used for {@link Ensured}s.
*
* Legal Values are:
*
* - constant = polling every 250ms
* - fibonacci = polling based on values taken in order from
* the fibonacci sequence
* - exponential = polling based on values taken in order from
* an exponential sequence (a rate of 50%)
* - random.fibonacci = polling based on randomized values taken in
* order from the fibonacci sequence
* - randomo.exponential = polling based on randomized values taken in
* order from an exponential sequence
* (a rate of 50%)
*
*
* The default strategy is "random.fibonacci"
*/
public static final String ORACLETOOLS_DEFERRED_RETRY_STRATEGY = "oracletools.deferred.retry.strategy";
/**
* The maximum retry (timeout) that will be used for {@link Ensured}s.
*
* By default values are measured in milliseconds, however when
* time units are specified after the amounts eg:
* (ms = milliseconds, m = minutes, s = seconds, h = hours),
* conversions are automatically made to milliseconds.
*/
public static final String ORACLETOOLS_DEFERRED_RETRY_TIMEOUT = "oracletools.deferred.retry.timeout";
/**
* The default maximum retry timeout to use (in seconds) when a timeout
* is not configured or specified.
*/
public static final long ORACLETOOLS_DEFERRED_RETRY_TIMEOUT_SECS = 30;
/**
* A {@link ThreadLocal} to capture the most recent {@link Deferred}
* method call on a proxy created by {@link #invoking(Deferred)}.
*
* See {@link #invoking(Deferred)} and {@link #eventually(Object)}
* for more information.
*/
private static final ThreadLocal> m_deferred = new ThreadLocal>();
/**
* Obtains a {@link Deferred} representation of an {@link AtomicLong}.
*
* @param atomic the atomic value to be deferred
*
* @return a {@link Deferred} for the atomic value
*/
public static Deferred deferred(AtomicLong atomic)
{
return new DeferredAtomicLong(atomic);
}
/**
* Obtains a {@link Deferred} representation of an {@link AtomicInteger}.
*
* @param atomic the atomic value to be deferred
*
* @return a {@link Deferred} for the atomic value
*/
public static Deferred deferred(AtomicInteger atomic)
{
return new DeferredAtomicInteger(atomic);
}
/**
* Obtains a {@link Deferred} representation of an {@link AtomicBoolean}.
*
* @param atomic the atomic value to be deferred
*
* @return a {@link Deferred} for the atomic value
*/
public static Deferred deferred(AtomicBoolean atomic)
{
return new DeferredAtomicBoolean(atomic);
}
/**
* Obtains the default configured retry durations {@link Iterator}
* (each value measured in milliseconds) that can be used with
* {@link Ensured}s.
*
* @return a new instance of the default configured retry durations
* {@link Iterator}
*/
public static Iterator getDefaultEnsuredRetryDurationsMS()
{
String strategy = System.getProperty(ORACLETOOLS_DEFERRED_RETRY_STRATEGY);
if (strategy == null)
{
strategy = "random.fibonacci";
}
strategy = strategy.trim().toLowerCase();
if (strategy.equals("random.fibonacci"))
{
return new RandomIterator(new FibonacciIterator());
}
else if (strategy.equals("random.exponential"))
{
return new RandomIterator(new ExponentialIterator(0, 50));
}
else if (strategy.equals("fibonacci"))
{
return new FibonacciIterator();
}
else if (strategy.equals("exponential"))
{
return new ExponentialIterator(0, 50);
}
else
{
// default to constant polling
return new ConstantIterator(250L);
}
}
/**
* Obtains the default timeout/maximum wait duration (in milliseconds)
* for {@link Ensured}s.
*
* @return the default timeout (in milliseconds)
*/
public static long getDefaultEnsuredTimeoutMS()
{
String timeOut = System.getProperty(ORACLETOOLS_DEFERRED_RETRY_TIMEOUT);
if (timeOut == null)
{
return TimeUnit.SECONDS.toMillis(ORACLETOOLS_DEFERRED_RETRY_TIMEOUT_SECS);
}
else
{
timeOut = timeOut.trim().toLowerCase();
TimeUnit timeUnit;
if (timeOut.endsWith("ms"))
{
timeUnit = TimeUnit.MILLISECONDS;
timeOut = timeOut.substring(timeOut.length() - 2).trim();
}
else if (timeOut.endsWith("s"))
{
timeUnit = TimeUnit.SECONDS;
timeOut = timeOut.substring(timeOut.length() - 1).trim();
}
else if (timeOut.endsWith("m"))
{
timeUnit = TimeUnit.MINUTES;
timeOut = timeOut.substring(timeOut.length() - 1).trim();
}
else if (timeOut.endsWith("h"))
{
timeUnit = TimeUnit.HOURS;
timeOut = timeOut.substring(timeOut.length() - 1).trim();
}
else
{
// assume milliseconds when there's no timeout unit
timeUnit = TimeUnit.MILLISECONDS;
}
return timeUnit.toMillis(Long.valueOf(timeOut));
}
}
/**
* Obtains an ensured of the specified {@link Deferred}
* (configured using default {@link Ensured} timeouts)
*
* @param deferred the {@link Deferred} to ensure
*
* @return an {@link Ensured} of the {@link Deferred}
*/
public static Deferred ensured(Deferred deferred)
{
return deferred instanceof Ensured ? deferred : new Ensured(deferred,
getDefaultEnsuredRetryDurationsMS(),
getDefaultEnsuredTimeoutMS());
}
/**
* Obtains an ensured of the specified {@link Deferred}.
*
* @param deferred the {@link Deferred} to ensure
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return an {@link Ensured} of the {@link Deferred}
*/
public static Deferred ensured(Deferred deferred,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
return deferred instanceof Ensured ? deferred : new Ensured(deferred,
getDefaultEnsuredRetryDurationsMS(),
totalRetryDurationUnits
.toMillis(totalRetryDuration));
}
/**
* Obtains an ensured of the specified {@link Deferred}.
*
* @param deferred the {@link Deferred} to ensure
* @param retryDelayDuration the time to wait between retrying
* @param retryDelayDurationUnits the {@link TimeUnit}s for the retry delay duration
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return an {@link Ensured} of the {@link Deferred}
*/
public static Deferred ensured(Deferred deferred,
long retryDelayDuration,
TimeUnit retryDelayDurationUnits,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
Iterator retryDurationsMS =
new ConstantIterator(retryDelayDurationUnits.toMillis(retryDelayDuration < 0
? 0 : retryDelayDuration));
return deferred instanceof Ensured ? deferred : new Ensured(deferred,
retryDurationsMS,
totalRetryDurationUnits
.toMillis(totalRetryDuration));
}
/**
* Obtains an {@link Ensured} of the specified {@link Deferred}.
*
* @param deferred the {@link Deferred} to ensure
* @param totalRetryDurationMS the maximum duration (in milliseconds) to retry
*
* @return an {@link Ensured} of the {@link Deferred}
*/
public static Deferred ensured(Deferred deferred,
long totalRetryDurationMS)
{
return deferred instanceof Ensured ? (Ensured) deferred : new Ensured(deferred,
getDefaultEnsuredRetryDurationsMS(),
totalRetryDurationMS);
}
/**
* Obtains the value of a value (this is the identity function for non-deferred).
*
* This method is provided for API symmetry.
*
* @param value the value
*
* @return the value
*/
public static T ensure(T value)
{
return value;
}
/**
* Obtains the value of a {@link Deferred}.
*
* This is functionally equivalent to calling {@link Deferred#get()} on
* {@link #ensured(Deferred)}.
*
* @param deferred the {@link Deferred} to ensure
*
* @return the value of the {@link Deferred}
*/
public static T ensure(Deferred deferred)
{
return ensured(deferred).get();
}
/**
* Obtains the value of a {@link Deferred}.
*
* This is functionally equivalent to calling {@link Deferred#get()} on
* {@link #ensure(Deferred, long, TimeUnit)}.
*
* @param deferred the {@link Deferred} to ensure
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return the value of the {@link Deferred}
*/
public static T ensure(Deferred deferred,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
return ensured(deferred, totalRetryDuration, totalRetryDurationUnits).get();
}
/**
* Obtains the value of a {@link Deferred}.
*
* This is functionally equivalent to calling {@link Deferred#get()} on
* {@link #ensure(Deferred, long, TimeUnit)}.
*
* @param deferred the {@link Deferred} to ensure
* @param retryDelayDuration the time to wait between retrying
* @param retryDelayDurationUnits the {@link TimeUnit}s for the retry delay duration
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return the value of the {@link Deferred}
*/
public static T ensure(Deferred deferred,
long retryDelayDuration,
TimeUnit retryDelayDurationUnits,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
return ensured(deferred,
retryDelayDuration,
retryDelayDurationUnits,
totalRetryDuration,
totalRetryDurationUnits).get();
}
/**
* Obtains the value of a {@link Deferred}.
*
* This is functionally equivalent to calling {@link Deferred#get()} on
* {@link #ensure(Deferred, long)}.
*
* @param deferred the {@link Deferred} to ensure
* @param totalRetryDurationMS the maximum duration (in milliseconds) to retry
*
* @return an {@link Ensured} of the {@link Deferred}
*/
public static T ensure(Deferred deferred,
long totalRetryDurationMS)
{
return ensured(deferred, totalRetryDurationMS).get();
}
/**
* Obtains an {@link Cached} of the specified {@link Deferred}.
*
* @param deferred the {@link Deferred} to cache
*
* @return a {@link Cached} of the {@link Deferred}
*/
public static Cached cached(Deferred deferred)
{
return deferred instanceof Cached ? (Cached) deferred : new Cached(deferred);
}
/**
* Obtains a {@link Deferred} representation of a Java Future.
*
* @param clzOfResult the {@link Class} of result from the
* {@link java.util.concurrent.Future}
* @param future the {@link java.util.concurrent.Future}
*
* @return a {@link Deferred}
*/
public static Deferred future(Class clzOfResult,
java.util.concurrent.Future future)
{
return new Future(clzOfResult, future);
}
/**
* Creates a dynamic proxy of an {@link Object}. The returned proxy will
* record interactions (method calls) against the proxy for the
* purposes of representing the calls as {@link Deferred}s.
*
* The results of interactions on the returned proxy are always non-sense
* and/or other dynamic proxies. To determine the actual result (as
* a {@link Deferred}), one must call {@link #eventually(Object)}.
*
* @param the type of {@link Object}
* @param object the {@link Object} to proxy
*
* @return a recording dynamic proxy of the {@link Object}
*/
public static T invoking(T object)
{
return invoking(new Existing(object));
}
/**
* Creates a dynamic proxy of a {@link Deferred} object. The returned proxy
* will record interactions (method calls) against the proxy for the
* purposes of representing the calls as {@link Deferred}s.
*
* The results of interactions on the returned proxy are always non-sense
* and/or other dynamic proxies. To determine the actual result (as
* a {@link Deferred}), one must call {@link #eventually(Object)}.
*
* @param the type of {@link Object}
* @param deferred the {@link Deferred} object to proxy
*
* @return a recording dynamic proxy of the {@link Object}
*/
public static T invoking(Deferred deferred)
{
// ensure that there are no other pending invoking calls on this thread
if (m_deferred.get() == null)
{
// set the current deferred as a thread local so that
// we can "eventually" evaluate and return it.
m_deferred.set(deferred);
// FUTURE: we should raise a soft exception here if the deferred
// class is final or perhaps native as we can't proxy them.
// create a proxy of the specified object class that will record
// methods calls on the object and represent them as a deferred on a thread local
return ReflectionHelper.createProxyOf(deferred.getDeferredClass(), new DeferredMethodInteceptor());
}
else
{
throw new UnsupportedOperationException("An attempt was made to call 'invoking' after a previous call was made outside an 'eventually'."
+ "Alternatively two or more calls to 'invoking' have been made sequentially."
+ "Calls to 'invoking' must be made inside an 'eventually' call.");
}
}
/**
* Obtains a {@link Deferred} representation of the last call to a
* dynamic proxy created with either {@link #invoking(Object)} or
* {@link #invoking(Deferred)}.
*
* @param t the value returned from an call to 'invoking'
*
* @return a {@link Deferred} representation of a previous call
* {@link #invoking(Object)}
*/
@SuppressWarnings("unchecked")
public static Deferred eventually(T t)
{
// get the last deferred value from invoking
Deferred deferred = (Deferred) m_deferred.get();
if (deferred == null)
{
deferred = t instanceof Deferred ? (Deferred) t : new Existing(t);
}
else
{
// clear the last invoking call
m_deferred.set(null);
}
return ensured(deferred);
}
/**
* Obtains a {@link Deferred} representation of the last call to a
* dynamic proxy created with either {@link #invoking(Object)} or
* {@link #invoking(Deferred)}. If there was no call to create a
* dynamic proxy, the provided deferred is ensured and returned.
*
* @param t the deferred value (usually returned from 'invoking')
*
* @return a {@link Deferred} representation of a previous call
* {@link #invoking(Object)}
*/
@SuppressWarnings("unchecked")
public static Deferred eventually(Deferred t)
{
// get the last deferred value from invoking
Deferred deferred = (Deferred) m_deferred.get();
if (deferred == null)
{
deferred = t;
}
else
{
// clear the last invoking call
m_deferred.set(null);
}
return ensured(deferred);
}
/**
* Obtains a {@link Deferred} representation of the last call to a
* dynamic proxy created with either {@link #invoking(Object)} or
* {@link #invoking(Deferred)}.
*
* @param t the value returned from an call to 'invoking'
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return a {@link Deferred} representation of a previous call
* {@link #invoking(Object)}
*/
@SuppressWarnings("unchecked")
public static Deferred eventually(T t,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
// get the last deferred value from invoking
Deferred deferred = (Deferred) m_deferred.get();
if (deferred == null)
{
deferred = t instanceof Deferred ? (Deferred) t : new Existing(t);
}
else
{
// clear the last invoking call
m_deferred.set(null);
}
return ensured(deferred, totalRetryDuration, totalRetryDurationUnits);
}
/**
* Obtains a {@link Deferred} representation of the last call to a
* dynamic proxy created with either {@link #invoking(Object)} or
* {@link #invoking(Deferred)}. If there was no call to create a
* dynamic proxy, the provided deferred is ensured and returned.
*
* @param t the deferred value (usually returned
* from 'invoking'
* @param totalRetryDuration the maximum duration for retrying
* @param totalRetryDurationUnits the {@link TimeUnit}s for the duration
*
* @return a {@link Deferred} representation of a previous call
* {@link #invoking(Object)}
*/
@SuppressWarnings("unchecked")
public static Deferred eventually(Deferred t,
long totalRetryDuration,
TimeUnit totalRetryDurationUnits)
{
// get the last deferred value from invoking
Deferred deferred = (Deferred) m_deferred.get();
if (deferred == null)
{
deferred = t;
}
else
{
// clear the last invoking call
m_deferred.set(null);
}
return ensured(deferred, totalRetryDuration, totalRetryDurationUnits);
}
/**
* A {@link MethodInterceptor} that records invocations against a
* {@link ThreadLocal} {@link Deferred}.
*/
private static class DeferredMethodInteceptor implements MethodInterceptor
{
/**
* {@inheritDoc}
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public Object intercept(Object self,
Method method,
Object[] args,
MethodProxy methodProxy) throws Throwable
{
// get the underlying deferred object on which this invocation really occurred
Deferred> deferred = DeferredHelper.m_deferred.get();
// replace the underlying deferred with a deferred method invocation
// representing the result of this invocation
DeferredHelper.m_deferred.set(new DeferredInvoke(deferred, method, args));
// determine a suitable return value based on the method return type.
// this value will actually be ignored as this method call is being
// deferred.
Class> resultType = method.getReturnType();
if (resultType.equals(Byte.class) || resultType.equals(byte.class))
{
return new Byte((byte) 0);
}
else if (resultType.equals(Short.class) || resultType.equals(short.class))
{
return new Short((short) 0);
}
else if (resultType.equals(Integer.class) || resultType.equals(int.class))
{
return new Integer(0);
}
else if (resultType.equals(Long.class) || resultType.equals(long.class))
{
return new Long(0);
}
else if (resultType.equals(Float.class) || resultType.equals(float.class))
{
return new Float(0.0);
}
else if (resultType.equals(Double.class) || resultType.equals(double.class))
{
return new Double(0.0);
}
else if (resultType.equals(Character.class) || resultType.equals(char.class))
{
return new Character(' ');
}
else if (resultType.equals(Boolean.class) || resultType.equals(boolean.class))
{
return new Boolean(false);
}
else if (resultType.equals(AtomicBoolean.class))
{
return new AtomicBoolean(false);
}
else if (resultType.equals(AtomicInteger.class))
{
return new AtomicInteger(0);
}
else if (resultType.equals(AtomicLong.class))
{
return new AtomicLong(0);
}
else if (resultType.isArray())
{
return Array.newInstance(resultType, 0);
}
else if (resultType.equals(String.class))
{
return "";
}
else
{
// as the return type is an object type, create a proxy of it
// so we can continue to capture and defer method calls
return ReflectionHelper.createProxyOf(resultType, new DeferredMethodInteceptor());
}
}
}
}