Skip to content

Instantly share code, notes, and snippets.

@hugithordarson
Last active December 11, 2021 18:28
Show Gist options
  • Save hugithordarson/de78813f7db2d3e97f33a8a7afbdda07 to your computer and use it in GitHub Desktop.
Save hugithordarson/de78813f7db2d3e97f33a8a7afbdda07 to your computer and use it in GitHub Desktop.
package strimillinn.xperimental.async;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention( RetentionPolicy.RUNTIME )
public @interface KVCAsync {}
package strimillinn.xperimental.async;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.appserver.WOContext;
import er.extensions.components.ERXComponent;
/**
* Components that inherit from this class can annotate their methods with @KVCAsync.
* Methods annotated with @KVCAsync will be run simultaneously, instead of synchronously.
* Useful if you have a component that contains a lot of heavy methods and some concurrency can help.
*/
public abstract class SMAsyncComponent extends ERXComponent {
private static final Logger logger = LoggerFactory.getLogger( SMAsyncComponent.class );
/**
* Stores a list of all keys annotated with @KVCAsync
*/
private Set<String> _asyncKeys;
/**
* Stores the values of asyncKeys, once they've been calculated.
*/
private Map<String, Object> _asyncValues;
public SMAsyncComponent( WOContext context ) {
super( context );
}
/**
* Indicates if you want to enable async resolution of methods annotated with @KVCAsync
*/
protected boolean useKVCAsync() {
return true;
}
/**
* Checks if the requested key is annotated with @KVCAsync. The first time such a method is requested,
* all keys annotated with @KVCAsync are calculated simultaneously and the results then cached.
*/
@Override
public Object valueForKey( String key ) {
if( useKVCAsync() ) {
if( isAsync( key ) ) {
if( _asyncValues == null ) {
populateAsyncValues();
}
return _asyncValues.get( key );
}
}
return super.valueForKey( key );
}
/**
* @return A list of names of methods annotated with @KVCAsync
*/
private Set<String> asyncKeys() {
if( _asyncKeys == null ) {
_asyncKeys = new HashSet<>();
for( Method method : getClass().getDeclaredMethods() ) {
if( method.isAnnotationPresent( KVCAsync.class ) ) {
_asyncKeys.add( method.getName() );
}
}
}
return _asyncKeys;
}
/**
* @return true if the given key references a method annotated with @KVCAsync
*/
private boolean isAsync( String key ) {
return asyncKeys().contains( key );
}
/**
* Populates the map of cached asynchronous values
*/
private void populateAsyncValues() {
_asyncValues = Collections.synchronizedMap( new HashMap<>() );
logger.debug( "Entering populateAsyncValues()" );
try {
CompletableFuture<?>[] futures = new CompletableFuture[asyncKeys().size()];
int i = 0;
for( String key : asyncKeys() ) {
futures[i++] = CompletableFuture.runAsync( () -> {
Long time = System.currentTimeMillis();
_asyncValues.put( key, super.valueForKey( key ) );
logger.info( "Populated KVCAsyncValue : " + key + " in " + (System.currentTimeMillis() - time) + "ms" );
} );
}
CompletableFuture.allOf( futures ).get();
}
catch( InterruptedException | ExecutionException e ) {
throw new RuntimeException( "Failed to populate async fields", e );
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment