Skip to content

Commit

Permalink
feat(bpmn-model): enable non-interrupting time cycle boundary events
Browse files Browse the repository at this point in the history
- allow different support levels for boundary events
- refactor event definition validations to their respective validators
- add utility classes to parse repeating time intervals
  • Loading branch information
npepinpe committed Nov 27, 2018
1 parent 89778d4 commit bcf57f5
Show file tree
Hide file tree
Showing 13 changed files with 695 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public B message(Consumer<MessageBuilder> messageBuilderConsumer) {
return myself;
}

public MessageEventDefinitionBuilder messageEventDefinition() {
final MessageEventDefinition eventDefinition = createEmptyMessageEventDefinition();
element.getEventDefinitions().add(eventDefinition);
return new MessageEventDefinitionBuilder(modelInstance, eventDefinition);
}

/**
* Sets an event definition for the given signal name. If already a signal with this name exists
* it will be used, otherwise a new signal is created.
Expand Down
106 changes: 106 additions & 0 deletions bpmn-model/src/main/java/io/zeebe/model/bpmn/util/time/Interval.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright © 2017 camunda services GmbH (info@camunda.com)
*
* Licensed 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 io.zeebe.model.bpmn.util.time;

import java.time.Duration;
import java.time.Period;
import java.time.format.DateTimeParseException;
import java.util.Objects;

/** Combines {@link java.time.Period}, and {@link java.time.Duration} */
public class Interval {
private final Period period;
private final Duration duration;

public Interval(Period period, Duration duration) {
this.period = period;
this.duration = duration;
}

public Period getPeriod() {
return period;
}

public Duration getDuration() {
return duration;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (!(o instanceof Interval)) {
return false;
}

final Interval interval = (Interval) o;
return Objects.equals(getPeriod(), interval.getPeriod())
&& Objects.equals(getDuration(), interval.getDuration());
}

@Override
public int hashCode() {
return Objects.hash(getPeriod(), getDuration());
}

@Override
public String toString() {
if (period.isZero()) {
return duration.toString();
}

if (duration.isZero()) {
return period.toString();
}

return period.toString() + duration.toString().substring(1);
}

/**
* Only supports a subset of ISO8601, combining both period and duration.
*
* @param text ISO8601 conforming interval expression
* @return parsed interval
*/
public static Interval parse(String text) {
final int timeOffset = text.lastIndexOf("T");
final Period period;
final Duration duration;

// to remain consistent with normal duration parsing which requires a duration to start with P
if (text.charAt(0) != 'P') {
throw new DateTimeParseException("Must start with P", text, 0);
}

if (timeOffset > 0) {
duration = Duration.parse(String.format("P%S", text.substring(timeOffset)));
} else {
duration = Duration.ZERO;
}

if (timeOffset == -1) {
period = Period.parse(text);
} else if (timeOffset > 1) {
period = Period.parse(text.substring(0, timeOffset));
} else {
period = Period.ZERO;
}

return new Interval(period, duration);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright © 2017 camunda services GmbH (info@camunda.com)
*
* Licensed 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 io.zeebe.model.bpmn.util.time;

import java.time.format.DateTimeParseException;
import java.util.Objects;

public class RepeatingInterval {
public static final String INTERVAL_DESGINATOR = "/";
public static final int INFINITE = -1;

private final int repetitions;
private final Interval interval;

public RepeatingInterval(int repetitions, Interval interval) {
this.repetitions = repetitions;
this.interval = interval;
}

public int getRepetitions() {
return repetitions;
}

public Interval getInterval() {
return interval;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}

if (!(o instanceof RepeatingInterval)) {
return false;
}

final RepeatingInterval repeatingInterval = (RepeatingInterval) o;

return getRepetitions() == repeatingInterval.getRepetitions()
&& Objects.equals(getInterval(), repeatingInterval.getInterval());
}

@Override
public int hashCode() {
return Objects.hash(getRepetitions(), getInterval());
}

public static RepeatingInterval parse(String text) {
return parse(text, INTERVAL_DESGINATOR);
}

/**
* Parses a repeating interval as two parts, separated by a given interval designator.
*
* <p>The first part describes how often the interval should be repeated, and the second part is
* the interval itself; see {@link Interval#parse(String)} for more on parsing the interval.
*
* <p>The repeating part is conform to the following format: R[0-9]*
*
* <p>If given an interval with, e.g. the interval designator is not present in the text, it is
* assumed implicitly that the interval is meant to be repeated infinitely.
*
* @param text text to parse
* @param intervalDesignator the separator between the repeating and interval texts
* @return a RepeatingInterval based on the given text
*/
public static RepeatingInterval parse(String text, String intervalDesignator) {
final int intervalDesignatorOffset = text.indexOf(intervalDesignator);
int repetitions = INFINITE;
final Interval interval;

if (text.charAt(0) != 'R') {
throw new DateTimeParseException("Repetition spec must start with R", text, 0);
}

if (intervalDesignatorOffset == -1 || intervalDesignatorOffset == text.length() - 1) {
throw new DateTimeParseException("No interval given", text, intervalDesignatorOffset);
}

final String intervalText = text.substring(intervalDesignatorOffset + 1);
interval = Interval.parse(intervalText);

if (intervalDesignatorOffset > 1) {
final String repetitionsText = text.substring(1, intervalDesignatorOffset);

try {
repetitions = Integer.parseInt(repetitionsText);
} catch (NumberFormatException e) {
throw new DateTimeParseException("Cannot parse repetitions count", repetitionsText, 1, e);
}
}

return new RepeatingInterval(repetitions, interval);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,24 @@
*/
package io.zeebe.model.bpmn.validation.zeebe;

import io.zeebe.model.bpmn.impl.instance.MessageEventDefinitionImpl;
import io.zeebe.model.bpmn.impl.instance.TimerEventDefinitionImpl;
import io.zeebe.model.bpmn.instance.BoundaryEvent;
import io.zeebe.model.bpmn.instance.EventDefinition;
import io.zeebe.model.bpmn.instance.MessageEventDefinition;
import io.zeebe.model.bpmn.instance.TimerEventDefinition;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import org.camunda.bpm.model.xml.validation.ModelElementValidator;
import org.camunda.bpm.model.xml.validation.ValidationResultCollector;

public class BoundaryEventValidator implements ModelElementValidator<BoundaryEvent> {
private static final List<Class<? extends EventDefinition>> SUPPORTED_EVENTS =
Arrays.asList(TimerEventDefinition.class, MessageEventDefinition.class);
private static final Map<Class<? extends EventDefinition>, SupportLevel> SUPPORTED_EVENTS =
new HashMap<>();

static {
SUPPORTED_EVENTS.put(TimerEventDefinitionImpl.class, SupportLevel.All);
SUPPORTED_EVENTS.put(MessageEventDefinitionImpl.class, SupportLevel.Interrupting);
}

@Override
public Class<BoundaryEvent> getElementType() {
Expand All @@ -48,10 +53,6 @@ public void validate(BoundaryEvent element, ValidationResultCollector validation
validationResultCollector.addError(0, "Must have at least one outgoing sequence flow");
}

if (!element.cancelActivity()) {
validationResultCollector.addError(0, "Non-interrupting boundary events are not supported");
}

validateEventDefinition(element, validationResultCollector);
}

Expand All @@ -64,10 +65,39 @@ private void validateEventDefinition(
} else {
final EventDefinition eventDefinition = eventDefinitions.iterator().next();
final Class<? extends EventDefinition> type = eventDefinition.getClass();
final SupportLevel supportLevel = SUPPORTED_EVENTS.getOrDefault(type, SupportLevel.None);

if (SUPPORTED_EVENTS.stream().noneMatch(c -> c.isAssignableFrom(type))) {
validationResultCollector.addError(0, "Event definition must be one of: timer, message");
}
validateSupportLevel(element, validationResultCollector, supportLevel);
}
}

private void validateSupportLevel(
BoundaryEvent element,
ValidationResultCollector validationResultCollector,
SupportLevel supportLevel) {
switch (supportLevel) {
case None:
validationResultCollector.addError(0, "Boundary events must be one of: timer, message");
break;
case Interrupting:
if (!element.cancelActivity()) {
validationResultCollector.addError(
0, "Non-interrupting events of this type are not supported");
}
break;
case NonInterrupting:
if (element.cancelActivity()) {
validationResultCollector.addError(
0, "Interrupting events of this type are not supported");
}
break;
}
}

public enum SupportLevel {
None,
Interrupting,
NonInterrupting,
All,
}
}

This file was deleted.

Loading

0 comments on commit bcf57f5

Please sign in to comment.