Created
December 16, 2015 01:02
-
-
Save thomasdarimont/eb1bc1d24ccf3decbdc6 to your computer and use it in GitHub Desktop.
Define method execution order via Method references.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.tdlabs.test; | |
import java.io.Serializable; | |
import java.lang.invoke.SerializedLambda; | |
import java.lang.reflect.Method; | |
import java.util.Map; | |
import java.util.TreeMap; | |
import org.objectweb.asm.ClassReader; | |
import org.objectweb.asm.ClassVisitor; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Opcodes; | |
import org.objectweb.asm.Type; | |
@ScenarioTest | |
public class WebSecurityScenarioTest { | |
public Steps steps() { | |
Steps steps = new Steps(); | |
steps.add(this::visitPageRequiringAuthorizationWhileNotLoggedIn); | |
steps.add(this::login); | |
steps.add(this::visitSecondPageRequiringAuthorizationWhileLoggedIn); | |
steps.add(this::logout); | |
return steps; | |
} | |
@Step | |
void visitPageRequiringAuthorizationWhileNotLoggedIn() { | |
// attempt to visit page which requires that a user is logged in | |
// assert user is redirected to login page | |
} | |
@Step | |
void visitSecondPageRequiringAuthorizationWhileLoggedIn() { | |
// visit another page which requires that a user is logged in | |
// assert user can access page | |
} | |
@Step | |
void logout() { | |
// visit logout URL | |
// assert user has been logged out | |
} | |
@Step | |
void login() { | |
// submit login form with valid credentials | |
// assert user is redirected back to previous page requiring authorization | |
} | |
public static void main(String[] args) { | |
new WebSecurityScenarioTest().steps().print(); | |
} | |
} | |
class Steps { | |
Map<Integer, Method> methodMap = new TreeMap<>(); | |
void add(SerializableRunnable step) { | |
registerStep(methodMap.size(), step); | |
} | |
public void print() { | |
for (Map.Entry<Integer, Method> entry : methodMap.entrySet()) { | |
System.out.printf("%s %s%n", entry.getKey(), entry.getValue()); | |
} | |
} | |
private void registerStep(Integer key, Object step) { | |
try { | |
SerializedLambda lambda = tryConvertToSerializedLambda(step); | |
ClassReader cr = new ClassReader(lambda.getImplClass()); | |
MethodReferenceAnalyzingClassVisitor classVisitor = new MethodReferenceAnalyzingClassVisitor(lambda); | |
cr.accept(classVisitor, 0); | |
methodMap.put(key, classVisitor.getActualTargetMethod()); | |
} catch (Exception ex) { | |
ex.printStackTrace(); | |
} | |
} | |
private <T> SerializedLambda tryConvertToSerializedLambda(Object sup) throws Exception { | |
Method writeReplaceMethod = sup.getClass().getDeclaredMethod("writeReplace"); | |
writeReplaceMethod.setAccessible(true); | |
Object replacement = writeReplaceMethod.invoke(sup); | |
if (!(replacement instanceof SerializedLambda)) { | |
throw new RuntimeException(); | |
} | |
return (SerializedLambda) replacement; | |
} | |
} | |
@interface ScenarioTest { | |
} | |
@interface Step { | |
} | |
interface SerializableRunnable extends Runnable, Serializable {} | |
class MethodReferenceAnalyzingClassVisitor extends ClassVisitor implements Opcodes { | |
private final LambdaMethodExtractingMethodVisitor methodVisitor; | |
private SerializedLambda lambda; | |
public MethodReferenceAnalyzingClassVisitor(SerializedLambda lambda) { | |
super(ASM5); | |
this.methodVisitor = new LambdaMethodExtractingMethodVisitor(ASM5); | |
this.lambda = lambda; | |
} | |
public Method getActualTargetMethod() { | |
return methodVisitor.getMethod(); | |
} | |
@Override | |
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { | |
if (!name.equals(lambda.getImplMethodName())) { | |
return null; | |
} | |
return methodVisitor; | |
} | |
} | |
class LambdaMethodExtractingMethodVisitor extends MethodVisitor implements Opcodes { | |
private Method method; | |
public LambdaMethodExtractingMethodVisitor(int api) { | |
super(api); | |
} | |
@Override | |
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { | |
try { | |
Class<?> targetMethodOwner = Class.forName(owner.replace('/', '.'), false, getClass().getClassLoader()); | |
Class<?>[] paramTypes = convertDescToParameterTypes(desc); | |
this.method = targetMethodOwner.getDeclaredMethod(name, paramTypes); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private Class<?>[] convertDescToParameterTypes(String desc) throws ClassNotFoundException { | |
Type[] argumentTypes = Type.getArgumentTypes(desc); | |
if (argumentTypes.length == 0) { | |
return new Class[0]; | |
} | |
Class<?>[] argumentClasses = new Class[argumentTypes.length]; | |
for (int i = 0; i < argumentClasses.length; i++) { | |
argumentClasses[i] = Class.forName(argumentTypes[i].getClassName(), false, getClass().getClassLoader()); | |
} | |
return argumentClasses; | |
} | |
public Method getMethod() { | |
return method; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment