/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.beam.sdk.transforms.windowing; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import org.apache.beam.sdk.annotations.Experimental; import org.apache.beam.sdk.transforms.windowing.Trigger.OnceTrigger; import org.joda.time.Duration; import org.joda.time.Instant; import org.joda.time.format.PeriodFormat; import org.joda.time.format.PeriodFormatter; /** * A {@link Trigger} trigger that fires at a specified point in processing time, relative to when * input first arrives. */ @Experimental(Experimental.Kind.TRIGGER) public class AfterProcessingTime extends OnceTrigger { private static final PeriodFormatter DURATION_FORMATTER = PeriodFormat.wordBased(Locale.ENGLISH); private final List<TimestampTransform> timestampTransforms; private AfterProcessingTime(List<TimestampTransform> timestampTransforms) { super(null); this.timestampTransforms = timestampTransforms; } /** * Creates a trigger that fires when the current processing time passes the processing time at * which this trigger saw the first element in a pane. */ public static AfterProcessingTime pastFirstElementInPane() { return new AfterProcessingTime(Collections.<TimestampTransform>emptyList()); } /** * The transforms applied to the arrival time of an element to determine when this trigger allows * output. */ public List<TimestampTransform> getTimestampTransforms() { return timestampTransforms; } /** * Adds some delay to the original target time. * * @param delay the delay to add * @return An updated time trigger that will wait the additional time before firing. */ public AfterProcessingTime plusDelayOf(final Duration delay) { return new AfterProcessingTime( ImmutableList.<TimestampTransform>builder() .addAll(timestampTransforms) .add(TimestampTransform.delay(delay)) .build()); } /** * Aligns timestamps to the smallest multiple of {@code period} since the {@code offset} greater * than the timestamp. */ public AfterProcessingTime alignedTo(final Duration period, final Instant offset) { return new AfterProcessingTime( ImmutableList.<TimestampTransform>builder() .addAll(timestampTransforms) .add(TimestampTransform.alignTo(period, offset)) .build()); } /** * Aligns the time to be the smallest multiple of {@code period} greater than the epoch * boundary (aka {@code new Instant(0)}). */ public AfterProcessingTime alignedTo(final Duration period) { return alignedTo(period, new Instant(0)); } @Override public boolean isCompatible(Trigger other) { return this.equals(other); } @Override public Instant getWatermarkThatGuaranteesFiring(BoundedWindow window) { return BoundedWindow.TIMESTAMP_MAX_VALUE; } @Override protected Trigger getContinuationTrigger(List<Trigger> continuationTriggers) { return AfterSynchronizedProcessingTime.ofFirstElement(); } @Override public String toString() { StringBuilder builder = new StringBuilder("AfterProcessingTime.pastFirstElementInPane()"); for (TimestampTransform transform : getTimestampTransforms()) { if (transform instanceof TimestampTransform.Delay) { TimestampTransform.Delay delay = (TimestampTransform.Delay) transform; builder .append(".plusDelayOf(") .append(DURATION_FORMATTER.print(delay.getDelay().toPeriod())) .append(")"); } else if (transform instanceof TimestampTransform.AlignTo) { TimestampTransform.AlignTo alignTo = (TimestampTransform.AlignTo) transform; builder .append(".alignedTo(") .append(DURATION_FORMATTER.print(alignTo.getPeriod().toPeriod())) .append(", ") .append(alignTo.getOffset()) .append(")"); } } return builder.toString(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof AfterProcessingTime)) { return false; } AfterProcessingTime that = (AfterProcessingTime) obj; return getTimestampTransforms().equals(that.getTimestampTransforms()); } @Override public int hashCode() { return Objects.hash(getTimestampTransforms()); } }