Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ public interface BpmnXMLConstants {
public static final String ATTRIBUTE_FORM_REQUIRED = "required";
public static final String ATTRIBUTE_FORM_DEFAULT = "default";
public static final String ATTRIBUTE_FORM_DATEPATTERN = "datePattern";
public static final String ATTRIBUTE_FORM_LENIENT_DATE_PARSING = "lenientDateParsing";
public static final String ELEMENT_VALUE = "value";

public static final String ELEMENT_FIELD = "field";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ protected boolean writeFormProperties(FlowElement flowElement, boolean didWriteE
writeDefaultAttribute(ATTRIBUTE_FORM_VARIABLE, property.getVariable(), xtw);
writeDefaultAttribute(ATTRIBUTE_FORM_DEFAULT, property.getDefaultExpression(), xtw);
writeDefaultAttribute(ATTRIBUTE_FORM_DATEPATTERN, property.getDatePattern(), xtw);
if (property.getLenientDateParsing() != null) {
writeDefaultAttribute(ATTRIBUTE_FORM_LENIENT_DATE_PARSING,
property.getLenientDateParsing() ? ATTRIBUTE_VALUE_TRUE : ATTRIBUTE_VALUE_FALSE, xtw);
}
if (!property.isReadable()) {
writeDefaultAttribute(ATTRIBUTE_FORM_READABLE, ATTRIBUTE_VALUE_FALSE, xtw);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public void parseChildElement(XMLStreamReader xtr, BaseElement parentElement, Bp
property.setExpression(xtr.getAttributeValue(null, ATTRIBUTE_FORM_EXPRESSION));
property.setDefaultExpression(xtr.getAttributeValue(null, ATTRIBUTE_FORM_DEFAULT));
property.setDatePattern(xtr.getAttributeValue(null, ATTRIBUTE_FORM_DATEPATTERN));
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_FORM_LENIENT_DATE_PARSING))) {
property.setLenientDateParsing(Boolean.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_FORM_LENIENT_DATE_PARSING)));
}
if (StringUtils.isNotEmpty(xtr.getAttributeValue(null, ATTRIBUTE_FORM_REQUIRED))) {
property.setRequired(Boolean.valueOf(xtr.getAttributeValue(null, ATTRIBUTE_FORM_REQUIRED)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ void validateModel(BpmnModel model) {

List<FormProperty> formProperties = userTask.getFormProperties();

assertThat(formProperties).as("Invalid form properties list: ").hasSize(8);
assertThat(formProperties).as("Invalid form properties list: ").hasSize(11);

for (FormProperty formProperty : formProperties) {
if ("new_property_1".equals(formProperty.getId())) {
Expand Down Expand Up @@ -78,6 +78,15 @@ void validateModel(BpmnModel model) {
checkFormProperty(formProperty, true, true, false);
} else if ("new_property_8".equals(formProperty.getId())) {
checkFormProperty(formProperty, true, true, true);
} else if ("date_property_default".equals(formProperty.getId())) {
assertThat(formProperty.getDatePattern()).isEqualTo("dd/MM/yyyy");
assertThat(formProperty.getLenientDateParsing()).isNull();
} else if ("date_property_lenient".equals(formProperty.getId())) {
assertThat(formProperty.getDatePattern()).isEqualTo("dd/MM/yyyy");
assertThat(formProperty.getLenientDateParsing()).isTrue();
} else if ("date_property_strict".equals(formProperty.getId())) {
assertThat(formProperty.getDatePattern()).isEqualTo("dd/MM/yyyy");
assertThat(formProperty.getLenientDateParsing()).isFalse();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
<activiti:formProperty id="new_property_6" name="v101" type="string" variable="v101" readable="false" required="true"></activiti:formProperty>
<activiti:formProperty id="new_property_7" name="v110" type="string" variable="v110" writable="false" required="true"></activiti:formProperty>
<activiti:formProperty id="new_property_8" name="v111" type="string" variable="v111" required="true"></activiti:formProperty>
<activiti:formProperty id="date_property_default" name="d1" type="date" datePattern="dd/MM/yyyy" variable="d1"></activiti:formProperty>
<activiti:formProperty id="date_property_lenient" name="d2" type="date" datePattern="dd/MM/yyyy" lenientDateParsing="true" variable="d2"></activiti:formProperty>
<activiti:formProperty id="date_property_strict" name="d3" type="date" datePattern="dd/MM/yyyy" lenientDateParsing="false" variable="d3"></activiti:formProperty>
</extensionElements>
</userTask>
<sequenceFlow id="sid-67E5C57C-B8CB-4B76-AF19-46A3DD03290E" sourceRef="startNode" targetRef="userTask"></sequenceFlow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class FormProperty extends BaseElement {
protected String type;
protected String defaultExpression;
protected String datePattern;
protected Boolean lenientDateParsing;
protected boolean readable = true;
protected boolean writeable = true;
protected boolean required;
Expand Down Expand Up @@ -79,6 +80,14 @@ public void setDatePattern(String datePattern) {
this.datePattern = datePattern;
}

public Boolean getLenientDateParsing() {
return lenientDateParsing;
}

public void setLenientDateParsing(Boolean lenientDateParsing) {
this.lenientDateParsing = lenientDateParsing;
}

public boolean isReadable() {
return readable;
}
Expand Down Expand Up @@ -126,6 +135,7 @@ public void setValues(FormProperty otherProperty) {
setType(otherProperty.getType());
setDefaultExpression(otherProperty.getDefaultExpression());
setDatePattern(otherProperty.getDatePattern());
setLenientDateParsing(otherProperty.getLenientDateParsing());
setReadable(otherProperty.isReadable());
setWriteable(otherProperty.isWriteable());
setRequired(otherProperty.isRequired());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ public abstract class ProcessEngineConfigurationImpl extends ProcessEngineConfig

protected List<AbstractFormType> customFormTypes;
protected FormTypes formTypes;
protected boolean lenientDateParsing;

protected List<VariableType> customPreVariableTypes;
protected List<VariableType> customPostVariableTypes;
Expand Down Expand Up @@ -2257,9 +2258,10 @@ public void initFormEngines() {
public void initFormTypes() {
if (formTypes == null) {
formTypes = new FormTypes();
formTypes.setLenientDateParsing(lenientDateParsing);
formTypes.addFormType(new StringFormType());
formTypes.addFormType(new LongFormType());
formTypes.addFormType(new DateFormType("dd/MM/yyyy"));
formTypes.addFormType(new DateFormType("dd/MM/yyyy", lenientDateParsing));
formTypes.addFormType(new BooleanFormType());
formTypes.addFormType(new DoubleFormType());
}
Expand Down Expand Up @@ -2993,6 +2995,15 @@ public ProcessEngineConfigurationImpl setFormTypes(FormTypes formTypes) {
return this;
}

public boolean isLenientDateParsing() {
return lenientDateParsing;
}

public ProcessEngineConfigurationImpl setLenientDateParsing(boolean lenientDateParsing) {
this.lenientDateParsing = lenientDateParsing;
return this;
}

@Override
public FlowableScriptEngine getScriptEngine() {
return scriptEngine;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* 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.
Expand All @@ -13,10 +13,10 @@

package org.flowable.engine.impl.form;

import java.text.Format;
import java.text.ParseException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.engine.form.AbstractFormType;
Expand All @@ -29,10 +29,16 @@ public class DateFormType extends AbstractFormType {
private static final long serialVersionUID = 1L;

protected String datePattern;
protected Format dateFormat;
protected boolean lenientDateParsing;
protected FastDateFormat dateFormat;

public DateFormType(String datePattern) {
this(datePattern, false);
}

public DateFormType(String datePattern, boolean lenientDateParsing) {
this.datePattern = datePattern;
this.lenientDateParsing = lenientDateParsing;
this.dateFormat = FastDateFormat.getInstance(datePattern);
}

Expand All @@ -55,7 +61,12 @@ public Object convertFormValueToModelValue(String propertyValue) {
return null;
}
try {
return dateFormat.parseObject(propertyValue);
if (lenientDateParsing) {
return dateFormat.parseObject(propertyValue);
}
// FastDateFormat is always lenient, so strict parsing uses DateUtils,
// which also rejects input with trailing characters after the date.
return DateUtils.parseDateStrictly(propertyValue, datePattern);
} catch (ParseException e) {
throw new FlowableIllegalArgumentException("invalid date value " + propertyValue, e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
public class FormTypes {

protected Map<String, AbstractFormType> formTypes = new HashMap<>();
protected boolean lenientDateParsing;

public void addFormType(AbstractFormType formType) {
formTypes.put(formType.getName(), formType);
Expand All @@ -38,7 +39,8 @@ public AbstractFormType parseFormPropertyType(FormProperty formProperty) {
AbstractFormType formType = null;

if ("date".equals(formProperty.getType()) && StringUtils.isNotEmpty(formProperty.getDatePattern())) {
formType = new DateFormType(formProperty.getDatePattern());
boolean lenient = formProperty.getLenientDateParsing() != null ? formProperty.getLenientDateParsing() : lenientDateParsing;
formType = new DateFormType(formProperty.getDatePattern(), lenient);

} else if ("enum".equals(formProperty.getType())) {
// ACT-1023: Using linked hashmap to preserve the order in which the
Expand All @@ -57,4 +59,12 @@ public AbstractFormType parseFormPropertyType(FormProperty formProperty) {
}
return formType;
}

public boolean isLenientDateParsing() {
return lenientDateParsing;
}

public void setLenientDateParsing(boolean lenientDateParsing) {
this.lenientDateParsing = lenientDateParsing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* 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 org.flowable.engine.impl.form;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.junit.jupiter.api.Test;

class DateFormTypeTest {

private final DateFormType dateFormType = new DateFormType("dd/MM/yyyy");

@Test
void nullValueReturnsNull() {
assertThat(dateFormType.convertFormValueToModelValue(null)).isNull();
}

@Test
void emptyValueReturnsNull() {
assertThat(dateFormType.convertFormValueToModelValue("")).isNull();
}

@Test
void validDateIsParsed() {
Object result = dateFormType.convertFormValueToModelValue("15/06/2024");
assertThat(result).isInstanceOf(Date.class);
}

@Test
void invalidMonthThrowsException() {
assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("15/13/2024"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void invalidDayThrowsException() {
assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("32/06/2024"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void trailingCharactersThrowException() {
assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("15/06/2024abc"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void wrongFormatThrowsException() {
assertThatThrownBy(() -> dateFormType.convertFormValueToModelValue("2024-06-15"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void differentFormatValidDateIsParsed() {
DateFormType isoFormat = new DateFormType("yyyy-MM-dd");
Object result = isoFormat.convertFormValueToModelValue("2024-06-15");
assertThat(result).isInstanceOf(Date.class);
}

@Test
void differentFormatInvalidDayThrowsException() {
DateFormType isoFormat = new DateFormType("yyyy-MM-dd");
assertThatThrownBy(() -> isoFormat.convertFormValueToModelValue("2024-06-32"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void inputValidForOtherFormatThrowsException() {
DateFormType isoFormat = new DateFormType("yyyy-MM-dd");
assertThatThrownBy(() -> isoFormat.convertFormValueToModelValue("15/06/2024"))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void lenientParsingRollsOverInvalidDate() {
DateFormType lenientFormType = new DateFormType("dd/MM/yyyy", true);
Object result = lenientFormType.convertFormValueToModelValue("15/13/2024");
assertThat(result).isEqualTo(new GregorianCalendar(2025, Calendar.JANUARY, 15).getTime());
}

@Test
void lenientParsingAcceptsValidDate() {
DateFormType lenientFormType = new DateFormType("dd/MM/yyyy", true);
Object result = lenientFormType.convertFormValueToModelValue("15/06/2024");
assertThat(result).isInstanceOf(Date.class);
}

@Test
void modelValueIsFormatted() {
Date date = new GregorianCalendar(2024, Calendar.JUNE, 15).getTime();
assertThat(dateFormType.convertModelValueToFormValue(date)).isEqualTo("15/06/2024");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* 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 org.flowable.engine.impl.form;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.util.Date;

import org.flowable.bpmn.model.FormProperty;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
import org.flowable.engine.form.AbstractFormType;
import org.junit.jupiter.api.Test;

class FormTypesTest {

private static final String INVALID_DATE = "15/13/2024";

private final FormTypes formTypes = new FormTypes();

@Test
void dateParsingIsStrictByDefault() {
AbstractFormType formType = formTypes.parseFormPropertyType(dateFormProperty(null));
assertThatThrownBy(() -> formType.convertFormValueToModelValue(INVALID_DATE))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

@Test
void engineDefaultLenientIsApplied() {
formTypes.setLenientDateParsing(true);
AbstractFormType formType = formTypes.parseFormPropertyType(dateFormProperty(null));
assertThat(formType.convertFormValueToModelValue(INVALID_DATE)).isInstanceOf(Date.class);
}

@Test
void formPropertyOverrideWinsOverStrictDefault() {
AbstractFormType formType = formTypes.parseFormPropertyType(dateFormProperty(true));
assertThat(formType.convertFormValueToModelValue(INVALID_DATE)).isInstanceOf(Date.class);
}

@Test
void formPropertyOverrideWinsOverLenientDefault() {
formTypes.setLenientDateParsing(true);
AbstractFormType formType = formTypes.parseFormPropertyType(dateFormProperty(false));
assertThatThrownBy(() -> formType.convertFormValueToModelValue(INVALID_DATE))
.isInstanceOf(FlowableIllegalArgumentException.class);
}

private FormProperty dateFormProperty(Boolean lenientDateParsing) {
FormProperty formProperty = new FormProperty();
formProperty.setId("dateProperty");
formProperty.setType("date");
formProperty.setDatePattern("dd/MM/yyyy");
formProperty.setLenientDateParsing(lenientDateParsing);
return formProperty;
}
}
Loading