Custom smart service plug-ins are reusable code objects built using annotated Java classes, XML, and resource bundles. They can be used within your Process Models in the same manner as any other smart service, appearing in the Process Modeler for use in any process application.
See also: Smart Services
Building your custom smart service as a plug-in is the recommended approach, because it greatly simplifies the creation and deployment of your custom smart service. Plug-ins are based on the OSGi model, an industry standard approach to packaging modular functionality.
appian-bundled-plugins.zip
.See also: Appian Plug-ins
To create and deploy the smart service plug-in:
<APPIAN_HOME>/_admin/sdk/appian-plug-in-sdk.jar
src
folder.META-INF
in the folder name field and click Finish.META-INF
folder selected, right-click and select New > Folder.lib
in the folder name field and click Finish.src
folder selected, right-click and select New > Package.com.example.plugins.<YOUR_PLUGIN>
Click Finish.
Your file structure should appear similar to the following:
1
2
3
4
5
|_src
|_com.example.plugins.<YOUR_PLUGIN>
|
|_ META-INF
|_ lib
src/META-INF/lib/
folder.
src/META-INF/lib/
folder.src/com.example.plugins.<YOUR_PLUGIN>
package.
com.example.pluginname
).AppianSmartService
— OR — Type com.appiancorp.suiteapi.process.framework.AppianSmartService
.com.appiancorp.suiteapi
.appian-plugin.xml
file.
See below: Configuring the appian-plugin.xml Filesrc/com.example.plugins.<YOUR_PLUGIN>
package.
See below: InternationalizationYour plug-in is deployed. The plug-ins framework locates the new JAR file and deploys your custom smart service when the application server starts.
A custom Smart Service is built using a class that extends AppianSmartService.
The plug-in uses a Java class that defines the function to be performed when the node executes. This class extends the AppianSmartService
class and contains the logic for the smart service.
com/appiancorp/suiteapi/process/framework/AppianSmartService.java
AppianSmartService, whose run
method is overridden.
Inputs (Activity Class Parameters) and Outputs (Activity Class Return Variables) are defined using public getter and setter methods within your class.
set<InputName>
methods return void
and take a single parameter.
@Name
annotation. For example: @Name("NewTopic")
. Names should use camel case. (Capitalize each word and do not use spaces.)<InputName>
where each capital letter in your (camel case) getter or setter method name indicates a new word in the display name.
Long
parameter and annotating the method with a type annotation.null
value to a Java primitive is not possible.Required.OPTIONAL
designation when upgrading custom smart services from earlier versions (before Appian 6.0.3) which used a value of 2
for the required element in the activity class parameter schema.get<OutputName>
methods are used without an explicit setter.
com.appiancorp.suiteapi.process.framework.Required.ALWAYS
is the default setting.OPTIONAL
allows the process designer to select whether a form element that maps to the input is required or not).enumerations
element in the appian-plugin.xml
.true
using a default value.
@Input(defaultValue={"false","true","true"})
appian-plugin.xml
.defaultValue
attribute of the @Input
annotation, default values can be defined in XML in the smart-service element of the appian-plugin.xml
.Both complex and primitive system data types can be used by your plug-ins.
@Type
annotations can be used on getter and setter methods to indicate which Appian type they take or return.
Annotation | Appian Type | Definition |
---|---|---|
@GroupDataType | Group | @Type(namespace = Type.APPIAN_NAMESPACE, name="Group") |
@PageDataType | Page | @Type(namespace = Type.APPIAN_NAMESPACE, name="Page") |
@UserOrGroupDataType | User or Group | @Type(namespace = Type.APPIAN_NAMESPACE, name="UserOrGroup") |
@EmailAddressDataType | Email Address | @Type(namespace = Type.APPIAN_NAMESPACE, name="EmailAddress") |
@EmailRecipientDataType | Email Recipient | @Type(namespace = Type.APPIAN_NAMESPACE, name="EmailRecipient") |
@DocumentOrFolderDataType | Document or Folder | @Type(namespace = Type.APPIAN_NAMESPACE, name="ContentItem") |
@DocumentDataType | Document | @Type(namespace = Type.APPIAN_NAMESPACE, name="Document") |
@FolderDataType | Folder | @Type(namespace = Type.APPIAN_NAMESPACE, name="Folder") |
@KnowledgeCenterDataType | Knowledge Center | @Type(namespace = Type.APPIAN_NAMESPACE, name="KnowledgeCenter") |
@CommunityDataType | Community | @Type(namespace = Type.APPIAN_NAMESPACE, name="Community") |
@UserDataType | User | @Type(namespace = Type.APPIAN_NAMESPACE, name="User") |
@PasswordDataType | Password | @Type(namespace = Type.APPIAN_NAMESPACE, name="Password") |
The table below maps Java data types to Appian data types. Arrays of a Java Data type []
map to the corresponding multiple-value Appian data type. The Java data types listed are the only types supported for inferring an Appian data type.
Java Type(s) | Appian Type |
---|---|
java.lang.Boolean, primitive boolean | Boolean |
java.lang.Integer, primitive int | Number (Integer) |
java.lang.Long, primitive long | Number (Integer) |
java.lang.Double, primitive double | Number (Decimal) |
java.lang.String | Text |
java.sql.Date | Date |
java.sql.Time | Time |
java.sql.Timestamp | Date & Time |
The following Java data types are expressly not supported for inferring an Appian data type:
java.lang.Byte, byte, byte[]
java.lang.Char, char
java.lang.Short, short
java.lang.Float, float
java.util.Currency
java.util.Date
You can reference a custom type using the @Type
annotation.
The table below lists a data type annotation example for custom data types.
Annotation | Definition |
---|---|
@Type | @Type (namespace="http://example.org", name="MyCustomType") |
Java objects can be used to create custom data types for use in your plug-ins.
See also: Custom Data Types from Java Object
The following properties and attributes of a smart service activity can be defined using attributes within your Java class:
@PaletteInfo
annotation.@Attended
and @Unattended
annotations.@Order
annotation.The following attributes of the @PaletteInfo
annotation define the palette placement for the smart service:
PaletteInfo
are defined for the standard smart service palette categories. The table below describes the standard palette annotations that you can use.paletteCategory
and palette
values when you need to deprecate a custom smart service: @PaletteInfo(paletteCategory="#Deprecated#", palette="#Deprecated#")
. Deprecated services will not appear in the palette, only via search.Annotation | Definition |
---|---|
@WorkflowHumanTasks | @PaletteInfo(paletteCategory = PaletteCategoryConstants.WORKFLOW, palette = PaletteConstants.HUMAN_TASKS) |
@WorkflowGateways | @PaletteInfo(paletteCategory = PaletteCategoryConstants.WORKFLOW, palette = PaletteConstants.GATEWAYS) |
@WorkflowEvents | @PaletteInfo(paletteCategory = PaletteCategoryConstants.WORKFLOW, palette = PaletteConstants.EVENTS) |
@WorkflowActivities | @PaletteInfo(paletteCategory = PaletteCategoryConstants.WORKFLOW, palette = PaletteConstants.ACTIVITIES) |
@AutomationSmartServicesTestManagement | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.TEST_MANAGEMENT) |
@AutomationSmartServicesSocial | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.SOCIAL) |
@AutomationSmartServicesRoboticProcesses | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.ROBOTIC_PROCESSES) |
@AutomationSmartServicesProcessManagement | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.PROCESS_MANAGEMENT) |
@AutomationSmartServicesIntegrationAPIs | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.INTEGRATION_APIS) |
@AutomationSmartServicesIdentityManagement | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.IDENTITY_MANAGEMENT) |
@AutomationSmartServicesDocumentManagement | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.DOCUMENT_MANAGEMENT) |
@AutomationSmartServicesDocumentGeneration | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.DOCUMENT_GENERATION) |
@AutomationSmartServicesDataServices | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.DATA_SERVICES) |
@AutomationSmartServicesCommunication | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.COMMUNICATION) |
@AutomationSmartServicesBusinessRules | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.BUSINESS_RULES) |
@AutomationSmartServiceAnalytics | @PaletteInfo(paletteCategory = PaletteCategoryConstants.AUTOMATION_SMART_SERVICES, palette = PaletteConstants.ANALYTICS) |
The @Attended
annotation is optional.
The @Unattended
annotation is also optional.
If neither the @Attended
nor the @Unattended
annotations are present, the process designer can determine whether the activity is attended or not.
The @Order
annotation accepts a list of input and output names in the order they should appear in the data tab. If not present, inputs appear in alphabetical order.
For example, if there is an input defined using a setMyInput()
method, list it first by setting @Order({"MyInput",...})
. If there is also an input defined using the @Name
annotation, such as @Name("TopicName"
), specify it before MyInput
by using @Order({"TopicName","MyInput",...})
.
Dynamic inputs can be added by the process designer, as with all smart services.
You can handle such inputs in your smart service by overriding the setDynamicInputs
method. The setDynamicInputs()
method gives you a List of NamedTypeValue objects, one for each input.
For example, the body of the setDynamicInputs
method could be used to store the inputs into a member variable, which can then be used in the body of your run
method implementation.
There is no public API for converting TypedValues to Java objects registered as data type plug-ins (POJO data types). Instead, use the methods available on TypedValue and the TypeService to handle the data.
Instances of certain interfaces can be injected into the smart service by declaring constructor parameters of their type.
SmartServiceContext
can be obtained by passing it in the constructor of the smart service class.
getUsername
method in SmartServiceContext
.*Service
interfaces in the public API (such as, ProcessAnalyticsService
, or ContentService
) can be obtained by passing them as parameters to the constructor.
EncryptionService
, the plug-in key must be granted access by an administrator in the Plug-ins page of the Admin Console.EncryptionService
from within the run()
method, not from within your smart service's getters and setters.com.appiancorp.suiteapi.collaboration.DocumentService
, are not injected. In place of the DocumentService, use the com.appiancorp.suiteapi.content.ContentService
.*Service
within custom Smart Service code. Do not call ServiceLocator.get*
.javax.naming.Context
can be used in the smart service by adding it as a constructor parameter.
lookup
method in Context
to get access to the objects that are registered in the JNDI tree, such as data sources.See also: Admin Console
Tip: The only way to access data sources is by injecting the javax.naming.Context
into your plug-in function constructor. Plug-in functions, smart services, and servlets using new InitialContext()
will fail when trying to access a data source configured in the Admin Console.
The following example illustrates how the InitialContext
should be injected into the constructor of the plug-in:
1
2
3
4
5
public MySmartService(SmartServiceContext smartServiceCtx, Context initialContext) {
super();
this.smartServiceCtx = smartServiceCtx;
this.initialContext = initialContext;
}
Smart services that integrate with external systems should use the Secure Credentials Store to securely handle the third-party credentials. The credentials stored in and retrieved from the Secure Credentials Store can be site-wide credentials, such as those used to represent a single integration user, or per-user credentials, where each individual user's credentials are used to authenticate against the external system. When using per-user credentials, the smart service must be attended or activity-chained and the user whose credentials are being used must be the user executing the smart service.
The SecureCredentialsStore
is injected just like other Appian services, by adding it to the constructor of the smart service. The injected SecureCredentialsStore
object provides a getSystemSecuredValues(String)
and a getUserSecuredValues(String)
method, which take the external system key as the parameter and returns a Map
of unencrypted values keyed by their corresponding attributes. You can only access the SecureCredentialsStore object from within the run()
method, not from within your smart service's getters and setters.
Before the credentials can be used by the plug-in, the following steps must be taken:
SecureCredentialsStore.getSystemSecuredValues(String)
or SecureCredentialsStore.getUserSecuredValues(String)
to obtain the map of credentials.Tip: Create your function to take the external system key as a parameter, and create a constant that holds the value of the generated key from the Third-Party Credentials page. Encourage designers to use the constant when calling the function.
See also:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class UpdateCustomerSmartService extends AppianSmartService {
private final SecureCredentialsStore scs;
private String externalSystemKey;
public UpdateCustomerSmartService(SecureCredentialsStore scs){
this.scs = scs;
}
@Input(required = Required.ALWAYS)
public setExternalSystemKey(String externalSystemKey) {
this.externalSystemKey = externalSystemKey;
}
public void run() throws SmartServiceException {
/* This example uses scs.getSystemSecuredValue to get the site-wide
* credentials for the external system. To get the map of per-user
* credentials, use scs.getUserSecuredValues
*/
Map credentials = scs.getSystemSecuredValues(externalSystemKey);
CRMClient client = //some client object provided by a 3rd party sdk
//The field name set as "Username" in the Third-Party Credentials page
client.setUsername(credentials.get("username"));
//The field name set as "Auth Token" in the Third-Party Credentials page
client.setAuthToken(credentials.get("auth.token"));
Connection conn = client.connect();
//use the authenticated connection to update the remote system
}
}
Smart services are validated when executed. The standard validation rules check a number of criteria for the user, including required inputs. You can optionally add your own validation to your smart service to ensure that expected values are provided by the process designer.
validate
method to define your custom validation.
MessageContainer
as a parameter.validate
method is called before the run
method for both attended and unattended activities.
MessageContainer
parameter with your error messages to be displayed to the user.MessageContainer
parameter using the addErrors
method, the form is not submitted and the run
method is not called.
onSave
method allows the developer to define whatever actions (typically validations) should take place when a user saves an attended form instead of submitting it.
onSave
method is only used to add validation routines that are applied when the user saves a draft of a form for completion at a later time.onSave
method adds any errors to the MessageContainer
parameter, the form is not saved and these messages appear on the form.validate
and onSave
methods are not required.In legacy smart services the validate method accepted a Boolean isSubmit
parameter, which you could use to determine whether validation was running due to form submission or when the form was saved. Now, validate
is only called when the form is submitted, and onSave
is only called when the form is saved.
The following example class is used by the Process Upgrade Smart Service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package com.appiancorp.ps.ss.processupgrade.process;
import org.apache.log4j.Logger;
import com.appiancorp.ps.ss.processupgrade.util.ProcessUpgradeDryRun;
import com.appiancorp.suiteapi.process.ProcessDesignService;
import com.appiancorp.suiteapi.process.ProcessExecutionService;
import com.appiancorp.suiteapi.process.ProcessModel;
import com.appiancorp.suiteapi.process.ProcessModel.Descriptor;
import com.appiancorp.suiteapi.process.analytics2.ProcessAnalyticsService;
import com.appiancorp.suiteapi.process.exceptions.SmartServiceException;
import com.appiancorp.suiteapi.process.framework.AppianSmartService;
import com.appiancorp.suiteapi.process.framework.Input;
import com.appiancorp.suiteapi.process.framework.Required;
import com.appiancorp.suiteapi.process.framework.SmartServiceContext;
import com.appiancorp.suiteapi.process.palette.ProcessManagement;
import com.appiancorp.suiteapi.process.upgrade.ProcessUpgrade;
import com.appiancorp.suiteapi.process.upgrade.ProcessUpgrade.Outcome;
@AutomationSmartServicesProcessManagement
public class UpgradeProcessesFromVersion extends AppianSmartService {
private static final Logger LOG = Logger.getLogger(UpgradeProcessesFromVersion.class);
private static final String USER_MSG_GETTING_MODEL = "error.gettingProcessModel.userMsg";
private static final String ALERT_MSG_GETTING_MODEL = "error.gettingProcessModel.alertMsg";
private static final String USER_MSG_GETTING_MODEL_VERSIONS = "error.gettingProcessModelVersions.userMsg";
private static final String ALERT_MSG_GETTING_MODEL_VERSIONS = "error.gettingProcessModelVersions.alertMsg";
private static final String USER_MSG_UPDATING = "error.updating.userMsg";
private static final String ALERT_MSG_UPDATING = "error.updating.alertMsg";
private SmartServiceContext ctx;
private ProcessDesignService pds;
private ProcessExecutionService pes;
private ProcessAnalyticsService pas;
private Boolean dryRun;
private String fromVersions[];
private Long fromProcessModelId;
private Long toProcessModelId;
private String toVersion;
private ProcessUpgrade processUpgrade[];
public UpgradeProcessesFromVersion(
SmartServiceContext ctx,
ProcessDesignService pds,
ProcessExecutionService pes,
ProcessAnalyticsService pas
) {
this.ctx = ctx;
this.pds = pds;
this.pes = pes;
this.pas = pas;
}
@Override
public void run() throws SmartServiceException {
if (fromProcessModelId == null || toProcessModelId == null)
return;
if (fromVersions == null || fromVersions.length == 0)
try {
Descriptor pmd[] = (Descriptor[]) (Descriptor[]) pds
.getVersionsForProcessModel(fromProcessModelId, 0, -1,
Integer.valueOf(1), Integer.valueOf(1))
.getResults();
fromVersions = new String[pmd.length];
for (int i = 0; i < pmd.length; i++)
fromVersions[i] = pmd[i].getVersion();
} catch (Exception e) {
throw createException(e, USER_MSG_GETTING_MODEL,
ALERT_MSG_GETTING_MODEL,
new Object[] { fromProcessModelId });
}
ProcessModel pm;
try {
if (toVersion == null || toVersion.equals("")) {
pm = pds
.getProcessModelLatestPublishedVersion(toProcessModelId);
toVersion = pm.getVersion();
} else {
pm = pds.getProcessModelVersion(
toProcessModelId, toVersion);
}
} catch (Exception e) {
throw createException(e,
USER_MSG_GETTING_MODEL_VERSIONS,
ALERT_MSG_GETTING_MODEL_VERSIONS,
new Object[] { fromProcessModelId });
}
if (LOG.isDebugEnabled())
LOG.debug((new StringBuilder()).append("Editing processes: ")
.append(getParametersDescription(pm)).toString());
try {
if (dryRun) {
processUpgrade = new ProcessUpgradeDryRun(pes, pds, pas).upgradeProcesses(
toProcessModelId, toVersion, fromProcessModelId,
fromVersions);
} else {
processUpgrade = pes.upgradeProcesses(
toProcessModelId, toVersion, fromProcessModelId,
fromVersions).getResults();
}
} catch (Exception e) {
throw createException(e, USER_MSG_UPDATING,
ALERT_MSG_UPDATING, new Object[] {
fromProcessModelId, pm.getUuid(),
getProcessModelVersions(), toProcessModelId,
toVersion });
}
if (LOG.isDebugEnabled())
LOG.debug((new StringBuilder())
.append("Finished editing processes: ")
.append(getParametersDescription(pm)).toString());
}
@Input(required = Required.ALWAYS, defaultValue = "true")
public void setDryRun(Boolean val) {
this.dryRun = val;
}
@Input(required = Required.ALWAYS)
public void setToProcessModelId(Long toProcessModelId) {
this.toProcessModelId = toProcessModelId;
}
@Input(required = Required.OPTIONAL)
public void setToVersion(String toVersion) {
this.toVersion = toVersion;
}
@Input(required = Required.ALWAYS)
public void setFromProcessModelId(Long fromProcessModelId) {
this.fromProcessModelId = fromProcessModelId;
}
@Input(required = Required.OPTIONAL)
public void setFromVersions(String fromVersions[]) {
this.fromVersions = fromVersions;
}
public boolean getWasSuccessful() {
Outcome outcomes[] = (Outcome[]) ProcessUpgrade.getOutcomes(processUpgrade);
for (Outcome outcome : outcomes) {
if (!outcome.isSuccessfulUpgrade()) {
return false;
}
}
return true;
}
public Long[] getResultProcessIds() {
return ProcessUpgrade.getProcessIds(processUpgrade);
}
public String[] getResultCodes() {
Outcome outcomes[] = (Outcome[]) ProcessUpgrade
.getOutcomes(processUpgrade);
String results[] = new String[outcomes.length];
int i = 0;
Outcome arr$[] = outcomes;
int len$ = arr$.length;
for (int i$ = 0; i$ < len$; i$++) {
Outcome outcome = arr$[i$];
results[i++] = outcome.toString();
}
return results;
}
private String getProcessModelVersions() {
StringBuilder msg = new StringBuilder();
for (int i = 0; i < fromVersions.length; i++) {
if (i != 0)
msg.append(", ");
msg.append(fromVersions[i]);
}
return msg.toString();
}
private String getParametersDescription(ProcessModel pm) {
return (new StringBuilder()).append("New process model [id: ")
.append(pm.getId()).append(", version: ")
.append(pm.getVersion()).append(", name: ")
.append(pm.getName().get(ctx.getUserLocale())).append(", uuid:")
.append(pm.getUuid()).append("], From process model: ")
.append(fromProcessModelId).append(", to Process Model: ")
.append(toProcessModelId).append(" fromVersions: ")
.append(getProcessModelVersions()).append(" toVersion: ")
.append(toVersion).append("]").toString();
}
private SmartServiceException createException(Throwable t, String userKey, String alertKey, Object args[]) {
SmartServiceException.Builder b = new SmartServiceException.Builder(getClass(), t);
b.userMessage(userKey, args);
b.alertMessage(alertKey, args);
b.addCauseToUserMessageArgs();
b.addCauseToAlertMessageArgs();
return b.build();
}
}
See also: Process Upgrade
Each Appian plug-in is defined using a file named appian-plugin.xml
. This file describes the configuration settings that are used by Appian to render your smart service in the Process Modeler, and execute it at runtime.
Each plug-in requires that an appian-plugin.xml
file appear in the root (highest level) of a Plug-in.
This file can use The following elements and attributes:
appian-plugin: (Required)
plugin-info
, one or more enumeration
, and one or more smart-service
child elements.key Attribute: (Required) A Unique identifier for each Plug-in.
appian.system.-
key, which is reserved for exclusive use by plug-ins that are bundled with the product.name Attribute: (Required) List a readable name for the Plug-in.
plugin-info: A subelement of appian-plugin
that contains the following required and optional child elements:
description: (Optional)
List a description for the Plug-in.
vendor: (Optional) A subelement of plugin-info
. It holds name
and url
attributes.
"Example Company"
"http://www.example.com"
version: (Required) A subelement of plugin-info
.
2.0.0
application-version: (Optional) A subelement of plugin-info
. It accepts the min
attribute, indicating a minimum product version.
"6.0.2"
smart-service: (Required) A subelement of appian-plugin
that contains the following attributes and elements:
local-id
attribute of the legacy Appian 5.x custom smart service.activity-class-schema: (Deprecated Element)
<activity-class-schema>
element for the smart service in your appian-plugin.xml
file. Activity class schemas are available to support backwards compatibility.Example appian-plugin.xml File
1
2
3
4
5
6
7
8
9
<appian-plugin name="Start Topic Smart Service" key="exampleOrganization.startTopicSmartService">
<plugin-info>
<description>Smart service used to start a topic</description>
<vendor name="Appian Corporation" url="http://www.appian.com" />
<version>1.0.0</version>
<application-version min="6.1" />
</plugin-info>
<smart-service name="Start Topic Smart Service" key="start-topic" class="com.appiancorp.process.runtime.activities.StartTopicSmartService" />
</appian-plugin>
Defining Default Forms
A default form can be defined as a dynamic form (a form that uses the Appian form components).
To define a dynamic form, include form-elements
(with form-element
subelements) to define a default form.
smart-service
> dynamic-form
element in your appian-plugin.xml file.label: A child element of form-element
that contains text, which appears within a message component.
type: When present, this type of component is used to specify the node input mapping. If this element is missing, the form component type is automatically inferred from the input type according to the Data Type Mappings table (below).
mapped-to: A child element of form-element
that defines the node input name, which the form input is mapped to.
The node input (ACP) name is determined by the text that follows after set-
for the setter methods of your Java class or by the @Name annotation if one is used to specifically set the node input name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<smart-service name="..." ...>
<form>
<dynamic-form>
<form-elements>
<form-element>
<label>LabelKey</label>
<type>17</type>
</form-element>
<form-element>
<mapped-to>TopicSubject</mapped-to>
</form-element>
</form-elements>
</dynamic-form>
</form>
</smart-service>
Data Type Mappings
The table below compares the ACP (node input) type with its associated AppianType mapping.
Input Type | AppianType |
---|---|
Number (Integer) | AppianType.INTEGER |
Number (Decimal) | AppianType.DOUBLE |
Text | AppianType.STRING |
User | AppianType.USERNAME |
Group | AppianType.GROUP |
User or Group | AppianType.USER_OR_GROUP |
Date | AppianType.DATE |
Time | AppianType.TIME |
Date & Time | AppianType.TIMESTAMP |
Knowledge Center | AppianType.CONTENT_KNOWLEDGE_CENTER |
Folder | AppianType.CONTENT_FOLDER |
Document | AppianType.CONTENT_DOCUMENT |
Community | AppianType.CONTENT_COMMUNITY |
Boolean | AppianType.BOOLEAN |
Document or Folder | AppianType.CONTENT_ITEM |
Password | AppianType.PASSWORD |
Email Address | AppianType.EMAIL_ADDRESS |
Email Recipient | AppianType.EMAIL_RECIPIENT |
Process Model | AppianType.PROCESS_MODEL |
Process | AppianType.PROCESS |
This optional subelement of the appian-plugin
element can be used to provide custom enumerations to your node inputs.
<enumeration>
tag per enumeration being added by this plugin.appian-plugin.xml
, the enumeration declaration must appear before any smart service that uses it.enumeration
attribute of the @Input
annotation is used to reference an enumeration in your class.For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
language:xml
<appian-plugin name="Issue Tracking Smart Service" key="com.example.issue-tracking-smart-service">
...
<enumeration key="issue-severity" type="3" >
<items>
<item>
<label>Fatal</label>
<detail>Severity: Fatal</detail>
<value>fatal</value>
</item>
<item>
<label>Major</label>
<detail>Severity: Major</detail>
<value>major</value>
</item>
<item>
<label>Normal</label>
<detail>Severity: Normal</detail>
<value>normal</value>
</item>
<item>
<label>Minor</label>
<detail>Severity: Minor</detail>
<value>minor</value>
</item>
</items>
</enumeration>
<smart-service name="Create Trouble Ticket Smart Service" key="trac-create-ticket"
class="com.example.services.CreateTracTicketActivity"/>
<smart-service name="Modify Ticket Smart Service" key="trac-modify-ticket"
class="com.example.services.ModifyTracTicketActivity"/>
</appian-plugin>
Schema
An enumeration includes the following attributes and subelements:
name{namespace}
enumeration/@type
annotation reference.
The table below lists the values accepted for TypedVariable
items used in enumerations.
Appian Typed Variable | ID |
---|---|
Integer | 1 |
Double | 2 |
String | 3 |
Username | 4 |
Group | 5 |
Currency | 6 |
Date | 7 |
Time | 8 |
Timestamp | 9 |
Binary | 10 |
Folder | 12 |
Document | 13 |
Portlet | 14 |
Page | 15 |
Knowledge Center | 19 |
Community | 20 |
Task | 21 |
Process | 22 |
Process Model | 23 |
Attachment | 24 |
Role | 25 |
Boolean | 26 |
User or Group | 27 |
Document or Folder | 28 |
Interval D S | 29 |
Simulation Scenario | 30 |
Note | 31 |
Password | 32 |
Event | 33 |
Email Address | 34 |
Email Recipient | 35 |
Content Rule | 36 |
Content Item | 37 |
Content Custom | 38 |
Content Freeform Rule | 39 |
Content Constant | 40 |
Group Type | 41 |
Fixed | 51 |
List of Folder | 112 |
List of Document | 113 |
List of Portlet | 114 |
List of Page | 115 |
List of Knowledge Center | 119 |
List of Community | 120 |
List of Task | 121 |
List of Process | 122 |
List of Process Model | 123 |
List of Attachment | 124 |
List of Role | 125 |
List of Boolean | 126 |
List of User or Group | 127 |
List of Document or Folder | 128 |
List of Interval D S | 129 |
List of Simulation Scenario | 130 |
List of Note | 131 |
List of Password | 132 |
List of Event | 133 |
List of Email Address | 134 |
List of Email Recipient | 135 |
List of Content Rule | 136 |
List of Content Item | 137 |
List of Content Custom | 138 |
List of Content Freeform Rule | 139 |
List of Content Constant | 140 |
List of Fixed | 151 |
List of Integer Key | 152 |
List of String Key | 153 |
List of Null | 157 |
List of Content Document | 160 |
List of Content Folder | 161 |
List of Content Knowledge Center | 162 |
List of Content Community | 163 |
List of Range | 172 |
List of Process Model Folder | 202 |
List of Links Channel Folder | 203 |
Bean | 11 |
External Reference | 78 |
External Reference with Indices | 79 |
Alias | 90 |
Array | 92 |
List | 93 |
Dictionary | 94 |
Record | 95 |
Union | 96 |
Variant | 97 |
List of External Reference | 78 |
List of External Reference with Indices | 81 |
List of Variant | 197 |
List of Bean | 111 |
List of Record | 195 |
Each plug-in should include an icon to display in the Process Modeler along with the other smart services.
palette-icon.svg
.canvas-icon.svg
./{plug-in key}/{smart service key}/images/
.
plug-in key
is com.example.pluginKey
, and the smart service key is yourKey
, store the images in the com.example.pluginKey.yourKey
package, in a folder named images
.<?xml version="1.0" encoding="UTF-8"?>
in the first line, or the SVG image will not display.Each Plug-in must provide internationalization bundles for the US English locale (en_US). All other locales are optional.
The following items must have keys and text strings listed in your resource bundles:
Currently, only error messages are read from bundles other than the default locale (en_US).
en_US
is the default locale for text that appears in the Process Modeler.
Resource bundle files are named according to the following pattern: </smart-service@key from appian-plugin.xml>_<locale>.properties
The bundle file is stored in the package structure where the plugin-key is broken down into a path (by switching dots for path separators).
/com/example/smart-services/trac-create-ticket_en_US.properties
Translation keys in your resource bundles must include the following items:
name
: the name of the smart service as it appears in the palette, ALT tags, and other places such as the canvas.
input.<InputName>.displayName
: The name of the input displayed on the Inputs tabbed property sheet, on the Data
tab of the smart service properties dialog.output.<OutputName>.displayName
: The name of the output (if any) that appears on the Outputs tabbed property sheet, on the Data tab of the smart service properties dialog.
MySmartServiceInput
is rendered as My Smart Service Input.input.<InputName>.comment
: The tooltip that appears when a user rests a pointer on a node input that appears on the Data tab.output.<OutputName>.comment
: the tooltip that appears when a user rests a pointer on an output that appears on the Data tab.
InputName
and OutputName
are the camelCase names (or @Name
annotated names) of the targets of the getter and setter methods (after removing the prepended get-
or set-
).The required upgrade approach varies depending on whether or not you have modified the node inputs or outputs of your smart service.
Changed Node Inputs/Outputs | No Changes to Node Inputs/Outputs |
---|---|
You must use a new key. | |
If you overwrite the plug-in, existing nodes may fail. | Changed plug-in logic must return similar values. |
To upgrade a production system, deprecate the previous plug-in. | Existing models may experience issues if plug-in logic changed. |
Deployment of a plug-in that contains new or updated smart services can cause a temporary degradation in process design and execution engine performance while the new smart services register with the engines. The duration of the performance degradation increases with the number of new and updated smart services as well as with the amount of data (process models and processes) in the system. Therefore, Appian recommends deploying plug-ins with new and updated smart services, especially those with a large number of affected smart services, during a period of low usage to minimize the impact on users. Deploying plug-ins with few or no new or updated smart services does not cause performance degradation.
The following conditions can cause plug-in deployment failures. If you encounter one of the following errors, you may need to change the smart service key in appian-plugin.xml to register the smart service as a new object rather than an update.
Issue | Description | Error Message |
---|---|---|
Duplicate Smart Service | Failed registration for duplicate name or identifier should, as the error explanation, report the duplicate identifier, indicating the type of object and the source of the conflict (the other bundle declaring the same identifier.) |
|
Smart Service with Custom Setup URL | Some legacy smart services are built with a setup tab that uses a custom JSP. Custom Setup URLs with JSPs are not supported. |
|
Inputs of Java Primitive Type | If a node input uses a Java primitive as a parameter, it must be required, or the plug-in fails to deploy. |
|
New plug-in version is incompatible with previous definition | If a new version of a smart service is uploaded (with the same key as an existing one), the system verifies the plug-in definition. If any of the following conditions change, deployment fails:
|
|
Missing bundle file | A plug-in fails to deploy if a required internationalization file is not present for one of the configured locales. |
|
Smart services that run as unattended must be able to complete within 60 minutes or it will pause by exception. The thread, however, running the code will not be terminated.
In order to individually build a custom smart service plug-in, a licensed local installation of Appian is required. Appian Cloud customers who have a local installation must develop and test the custom smart service locally and coordinate with Appian Technical Support to deploy it on their site.
Smart Service Plug-ins