
com.appian.connectedsystems.simplified.sdk.configuration.SimpleConfiguration Maven / Gradle / Ivy
package com.appian.connectedsystems.simplified.sdk.configuration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import com.appian.connectedsystems.simplified.sdk.SimpleConnectedSystemTemplate;
import com.appian.connectedsystems.simplified.sdk.SimpleIntegrationTemplate;
import com.appian.connectedsystems.templateframework.sdk.ExecutionContext;
import com.appian.connectedsystems.templateframework.sdk.configuration.ConfigurationDescriptor;
import com.appian.connectedsystems.templateframework.sdk.configuration.LocalTypeDescriptor;
import com.appian.connectedsystems.templateframework.sdk.configuration.PropertyDescriptor;
import com.appian.connectedsystems.templateframework.sdk.configuration.PropertyPath;
import com.appian.connectedsystems.templateframework.sdk.configuration.PropertyState;
import com.appian.connectedsystems.templateframework.sdk.configuration.StateGenerator;
/**
* Defines the configuration of an Appian Connected System object or an Appian Integration object.
*
* The following example illustrates defining the structure of the data and UI, followed by setting a value and an error:
*
*
* {@code
* integrationConfiguration.setProperties(
* textProperty("name")
* .label("Name")
* .build(),
* integerProperty("height")
* .label("Height")
* .build(),
* integerProperty("age")
* .label("Age")
* .build(),
* booleanProperty("requiresParentApproval")
* .label("Requires Parent Approval")
* .isReadOnly(true)
* .build());
*
* Integer age = integrationConfiguration.getValue("age");
*
* if (age != null)
* integrationConfiguration.setValue("requiresParentalApproval", age < 18);
*
* Integer heightInInches = integrationConfiguration.getValue("height");
* if (heightInInches != null && heightInInches < 60)
* integrationConfiguration.setError("height", "You are not tall enough to ride.");
* }
*
*
* Supports:
*
* - setting and retrieving values on properties by keyed locations
* - setting and retrieving errors on properties by keyed locations
* - creating the UI and data structure on the Appian object
*
*
* {@link #setProperties(PropertyDescriptor...)} must be called per invocation of
* {@link SimpleConnectedSystemTemplate#getConfiguration(SimpleConfiguration, ExecutionContext)} and
* {@link SimpleIntegrationTemplate#getConfiguration(SimpleConfiguration, SimpleConfiguration, PropertyPath, ExecutionContext)}
*
*
* As designers provide values, the SimpleConfiguration can be dynamically updated
* to display additional configuration options, fill in dropdown choices, and set default values.
*
*
*/
public class SimpleConfiguration {
static final String DEFAULT_ROOT_TYPE_NAME = "RootType";
private TypePropertyFactory typePropertyFactory;
private ExecutionContext executionContext;
private ConfigurationDescriptor configurationDescriptor;
private SimpleConfiguration(
ConfigurationDescriptor configurationDescriptor,
TypePropertyFactory typePropertyFactory,
ExecutionContext executionContext) {
this.typePropertyFactory = typePropertyFactory;
this.executionContext = executionContext;
if(configurationDescriptor == null) {
this.configurationDescriptor = ConfigurationDescriptor.builder().build();
this.setProperties();
} else {
this.configurationDescriptor = configurationDescriptor;
}
}
/**
* Defines the UI and data structure of the Appian object
*
*
* integrationConfiguration.setProperties(
* textProperty("name")
* .label("Name")
* .instructionText("Please enter your first and last name")
* .build(),
* integerProperty("height")
* .label("Height")
* .build(),
* integerProperty("age")
* .label("Age")
* .build(),
* booleanProperty("requiresParentApproval")
* .label("Requires Parent Approval")
* .description("If true, you need to ask your parents first!")
* .displayMode(BooleanDisplayMode.CHECKBOX)
* .isReadOnly(true)
* .build()
* )
*
*
* This method must be called per invocation of {@link SimpleConnectedSystemTemplate#getConfiguration(SimpleConfiguration, ExecutionContext)} and {@link SimpleIntegrationTemplate#getConfiguration(SimpleConfiguration, SimpleConfiguration, PropertyPath, ExecutionContext)}
*
*
* Properties define how fields are displayed.
* Each editable (non-read-only and non-hidden) property represents data the designer can configure.
* Values can be modified using {@link #setValue(String, Object)} or retrieved using {@link #getValue(String)}.
*
*
* All existing values are migrated to the new data structure based on the property keys. Newly added fields are set to default values defined by {@link com.appian.connectedsystems.templateframework.sdk.configuration.SystemType}.
*
* @param properties fields to be displayed in the UI
* @return updated SimpleConfiguration
*/
public SimpleConfiguration setProperties(PropertyDescriptor... properties) {
return setPropertiesGeneric(properties);
}
/**
*
* Properties define how fields are displayed.
* Each editable (non-read-only and non-hidden) property represents data the designer can configure.
* Values can be modified using {@link #setValue(String, Object)} or retrieved using {@link #getValue(String)}.
*
*
* @return list of properties that define the current UI
*/
public List getProperties() {
return configurationDescriptor.getTypes().get(DEFAULT_ROOT_TYPE_NAME).getProperties();
}
/**
*
* Properties define how fields are displayed.
* Each editable (non-read-only and non-hidden) property represents data the designer can configure.
* Values can be modified using {@link #setValue(String, Object)} or retrieved using {@link #getValue(String)}.
*
*
* @param key property key uniquely identifying the property.
* @return specific property of the configuration at the given key
*/
public PropertyDescriptor getProperty(String key) {
return configurationDescriptor.getTypes().get(DEFAULT_ROOT_TYPE_NAME).getProperty(key).orElse(null);
}
/**
* Gets a value stored in the configuration
*
* All values inside the configuration can be null. Use reference types.
*
* This value could be:
*
* - the default value on initialization. See {@link com.appian.connectedsystems.templateframework.sdk.configuration.SystemType}
* - set using {@link #setValue(String, Object)}
* - entered by a user
*
*
* @param key property key uniquely identifying the property
* @return T value at the given key
*/
public T getValue(String key) {
return getValue(new PropertyPath(key));
}
/**
* Gets a value stored anywhere in the configuration. Use this method to access nested properties
*
* All values inside the configuration can be null. Use reference types.
*
* This value could be:
*
* - the default value on initialization. See {@link com.appian.connectedsystems.templateframework.sdk.configuration.SystemType}
* - set using {@link #setValue(String, Object)}
* - entered by a user
*
*
* Fetch the value of height inside local property "personType"
*
* PropertyPath path = new PropertyPath("personType", "height");
* Integer height = integrationConfiguration.getValue(path);
*
*
* @param propertyPath list of property keys locating the property
* @return T value at the given key
*/
public T getValue(PropertyPath propertyPath) {
final PropertyState rootState = getRootState();
return (T)rootState.getValueAtPath(propertyPath);
}
/**
* Sets a value stored in the configuration.
*
* This will override any user-entered value at the same key.
*
* @param key property key uniquely identifying the property
* @param value value to insert at the key
* @return {@code this}
*/
public SimpleConfiguration setValue(
String key,
Object value) {
return setValue(new PropertyPath(key), value);
}
/**
* Sets a value stored anywhere in the configuration. Use this method to access nested properties
*
* This will override any user-entered value at the same path.
*
*
* PropertyPath path = new PropertyPath("personType", "height");
* int heightInInches = 72;
* integrationConfiguration.setValue(path, heightInInches);
*
*
* @param propertyPath list of property keys locating the property
* @param value value to insert at the key
* @return {@code this}
*/
public SimpleConfiguration setValue(
PropertyPath propertyPath,
Object value) {
final PropertyState rootState = getRootState();
rootState.setValueAtPath(propertyPath, value);
return this;
}
/**
* Set errors at the highest level of the configuration
*
* Use this method to show invalid configurations in the connected system
* or errors resulting from an invalid combination of multiple fields' configurations
*
* @param errors the errors to display
* @return {@code this}
*/
public SimpleConfiguration setErrors(List errors) {
final PropertyState rootState = getRootState();
rootState.setErrors(errors);
return this;
}
/**
* Sets an error in the configuration.
*
* This error is displayed as a validation on the property in the UI.
*
* @param key property key uniquely identifying the property
* @param errors list of validations
* @return {@code this}
*/
public SimpleConfiguration setErrors(
String key,
List errors) {
return setErrors(new PropertyPath(key), errors);
}
/**
* Sets an error anywhere in the configuration. Use this method to access nested properties
*
* This error is displayed as a validation on the property in the UI.
*
* @param propertyPath list of property keys locating the property
* @param errors list of validations
* @return {@code this}
*/
public SimpleConfiguration setErrors(
PropertyPath propertyPath,
List errors) {
final PropertyState rootState = getRootState();
rootState.setErrorsAtPath(propertyPath, errors);
return this;
}
public ConfigurationDescriptor toConfiguration() {
return configurationDescriptor;
}
public static SimpleConfiguration from(
ConfigurationDescriptor configDescriptor,
TypePropertyFactory typePropertyFactory,
ExecutionContext executionContext) {
typePropertyFactory = typePropertyFactory == null ? new TypePropertyFactory() : typePropertyFactory;
return new SimpleConfiguration(configDescriptor, typePropertyFactory, executionContext);
}
private String getRootTypeName() {
PropertyState rootState = getRootState();
String rootTypeName = DEFAULT_ROOT_TYPE_NAME;
if (rootState != null) {
rootTypeName = rootState.getType().getUnqualifiedTypeName();
}
return rootTypeName;
}
private SimpleConfiguration setPropertiesGeneric(PropertyDescriptor[] properties) {
return setPropertiesGeneric(getRootTypeName(), properties);
}
private SimpleConfiguration setPropertiesGeneric(String typeName, PropertyDescriptor[] properties) {
PropertyDescriptor[] builtProperties = filterProperties(properties);
LocalTypeDescriptor type = LocalTypeDescriptor.builder()
.name(typeName)
.properties(builtProperties)
.build();
Map newTypes = typePropertyFactory.getTypes();
newTypes.put(typeName, type);
return getConfigurationDescriptor(type, newTypes.values());
}
private PropertyState getRootState() {
return configurationDescriptor.getRootState();
}
private SimpleConfiguration getConfigurationDescriptor(
LocalTypeDescriptor root, Collection types) {
PropertyState state = new StateGenerator(types).generateFromExistingState(root, getRootState());
configurationDescriptor = ConfigurationDescriptor.builder()
.withTypes(types)
.withState(state)
.build();
return this;
}
private PropertyDescriptor[] filterProperties(PropertyDescriptor[] properties) {
Stream propStream = Arrays.stream(properties)
.filter(Objects::nonNull);
return propStream.toArray(PropertyDescriptor[]::new);
}
}