/** * Returns successfully if the field is valid, otherwise throws an exception via its {@link * ErrorReporter} parameter describing validation failures for the timer declaration. */ private static void validateTimerField( ErrorReporter errors, Map<String, TimerDeclaration> declarations, String id, Field field) { if (declarations.containsKey(id)) { errors.throwIllegalArgument( "Duplicate %s \"%s\", used on both of [%s] and [%s]", DoFn.TimerId.class.getSimpleName(), id, field.toString(), declarations.get(id).field().toString()); } Class<?> timerSpecRawType = field.getType(); if (!(timerSpecRawType.equals(TimerSpec.class))) { errors.throwIllegalArgument( "%s annotation on non-%s field [%s]", DoFn.TimerId.class.getSimpleName(), TimerSpec.class.getSimpleName(), field.toString()); } if (!Modifier.isFinal(field.getModifiers())) { errors.throwIllegalArgument( "Non-final field %s annotated with %s. Timer declarations must be final.", field.toString(), DoFn.TimerId.class.getSimpleName()); } }
/** * Returns an {@link MethodAnalysisContext} like this one but including the provided {@link * StateParameter}. */ public void addParameter(Parameter param) { extraParameters.add(param); if (param instanceof StateParameter) { StateParameter stateParameter = (StateParameter) param; stateParameters.put(stateParameter.referent().id(), stateParameter); } if (param instanceof TimerParameter) { TimerParameter timerParameter = (TimerParameter) param; timerParameters.put(timerParameter.referent().id(), timerParameter); } }
private static ImmutableMap<String, TimerDeclaration> analyzeTimerDeclarations( ErrorReporter errors, Class<?> fnClazz) { Map<String, DoFnSignature.TimerDeclaration> declarations = new HashMap<>(); for (Field field : declaredFieldsWithAnnotation(DoFn.TimerId.class, fnClazz, DoFn.class)) { // TimerSpec fields may generally be private, but will be accessed via the signature field.setAccessible(true); String id = field.getAnnotation(DoFn.TimerId.class).value(); validateTimerField(errors, declarations, id, field); declarations.put(id, DoFnSignature.TimerDeclaration.create(id, field)); } return ImmutableMap.copyOf(declarations); }
timerDecl.field().getDeclaringClass().equals(onTimerMethod.getDeclaringClass()), "Callback %s is for timer %s declared in a different class %s." + " Timer callbacks must be declared in the same lexical scope as their timer", onTimerMethod, id, timerDecl.field().getDeclaringClass().getCanonicalName()); onTimerMethodMap.containsKey(decl.id()), "No callback registered via %s for timer %s", DoFn.OnTimer.class.getSimpleName(), decl.id());
/** * In this particular test, the super class annotated both the timer and the callback, and the * subclass overrides an abstract method. This is allowed. */ @Test public void testOnTimerDeclaredAndUsedInSuperclass() throws Exception { DoFnSignature sig = DoFnSignatures.getSignature(new DoFnOverridingAbstractCallback().getClass()); assertThat(sig.timerDeclarations().size(), equalTo(1)); assertThat(sig.onTimerMethods().size(), equalTo(1)); DoFnSignature.TimerDeclaration decl = sig.timerDeclarations().get(DoFnDeclaringTimerAndAbstractCallback.TIMER_ID); DoFnSignature.OnTimerMethod callback = sig.onTimerMethods().get(DoFnDeclaringTimerAndAbstractCallback.TIMER_ID); assertThat( decl.field(), equalTo(DoFnDeclaringTimerAndAbstractCallback.class.getDeclaredField("myTimerSpec"))); // The method we pull out is the superclass method; this is what allows validation to remain // simple. The later invokeDynamic instruction causes it to invoke the actual implementation. assertThat( callback.targetMethod(), equalTo(DoFnDeclaringTimerAndAbstractCallback.class.getDeclaredMethod("onMyTimer"))); }
public static TimerSpec getTimerSpecOrThrow( TimerDeclaration timerDeclaration, DoFn<?, ?> target) { try { Object fieldValue = timerDeclaration.field().get(target); checkState( fieldValue instanceof TimerSpec, "Malformed %s class %s: timer declaration field %s does not have type %s.", DoFn.class.getSimpleName(), target.getClass().getName(), timerDeclaration.field().getName(), TimerSpec.class); return (TimerSpec) timerDeclaration.field().get(target); } catch (IllegalAccessException exc) { throw new RuntimeException( String.format( "Malformed %s class %s: timer declaration field %s is not accessible.", DoFn.class.getSimpleName(), target.getClass().getName(), timerDeclaration.field().getName())); } } }
@Test public void testSimpleTimerWithContext() throws Exception { DoFnSignature sig = DoFnSignatures.getSignature( new DoFn<KV<String, Integer>, Long>() { @TimerId("foo") private final TimerSpec bizzle = TimerSpecs.timer(TimeDomain.EVENT_TIME); @ProcessElement public void foo(ProcessContext context) {} @OnTimer("foo") public void onFoo(OnTimerContext c) {} }.getClass()); assertThat(sig.timerDeclarations().size(), equalTo(1)); DoFnSignature.TimerDeclaration decl = sig.timerDeclarations().get("foo"); assertThat(decl.id(), equalTo("foo")); assertThat(decl.field().getName(), equalTo("bizzle")); assertThat( sig.onTimerMethods().get("foo").extraParameters().get(0), equalTo((Parameter) Parameter.onTimerContext())); }
@Test public void testSimpleTimerIdAnonymousDoFn() throws Exception { DoFnSignature sig = DoFnSignatures.getSignature( new DoFn<KV<String, Integer>, Long>() { @TimerId("foo") private final TimerSpec bizzle = TimerSpecs.timer(TimeDomain.EVENT_TIME); @ProcessElement public void foo(ProcessContext context) {} @OnTimer("foo") public void onFoo() {} }.getClass()); assertThat(sig.timerDeclarations().size(), equalTo(1)); DoFnSignature.TimerDeclaration decl = sig.timerDeclarations().get("foo"); assertThat(decl.id(), equalTo("foo")); assertThat(decl.field().getName(), equalTo("bizzle")); }
@Test public void testSimpleTimerIdNamedDoFn() throws Exception { class DoFnForTestSimpleTimerIdNamedDoFn extends DoFn<KV<String, Integer>, Long> { @TimerId("foo") private final TimerSpec bizzle = TimerSpecs.timer(TimeDomain.EVENT_TIME); @ProcessElement public void foo(ProcessContext context) {} @OnTimer("foo") public void onFoo() {} } // Test classes at the bottom of the file DoFnSignature sig = DoFnSignatures.signatureForDoFn(new DoFnForTestSimpleTimerIdNamedDoFn()); assertThat(sig.timerDeclarations().size(), equalTo(1)); DoFnSignature.TimerDeclaration decl = sig.timerDeclarations().get("foo"); assertThat(decl.id(), equalTo("foo")); assertThat( decl.field(), equalTo(DoFnForTestSimpleTimerIdNamedDoFn.class.getDeclaredField("bizzle"))); }
@Test public void testDeclAndUsageOfTimerInSuperclass() throws Exception { DoFnSignature sig = DoFnSignatures.getSignature(new DoFnOverridingAbstractTimerUse().getClass()); assertThat(sig.timerDeclarations().size(), equalTo(1)); assertThat(sig.processElement().extraParameters().size(), equalTo(2)); DoFnSignature.TimerDeclaration decl = sig.timerDeclarations().get(DoFnOverridingAbstractTimerUse.TIMER_ID); TimerParameter timerParam = (TimerParameter) sig.processElement().extraParameters().get(1); assertThat( decl.field(), equalTo(DoFnDeclaringTimerAndAbstractUse.class.getDeclaredField("myTimerSpec"))); // The method we pull out is the superclass method; this is what allows validation to remain // simple. The later invokeDynamic instruction causes it to invoke the actual implementation. assertThat(timerParam.referent(), equalTo(decl)); }
public void addTimerDeclaration(TimerDeclaration decl) { timerDeclarations.put(decl.id(), decl); }
@Override public StackManipulation dispatch(TimerParameter p) { return new StackManipulation.Compound( new TextConstant(p.referent().id()), MethodInvocation.invoke( getExtraContextFactoryMethodDescription(TIMER_PARAMETER_METHOD, String.class)), TypeCasting.to(new TypeDescription.ForLoadedType(Timer.class))); }