OverviewCopy link to clipboard
Custom smart service plug-ins are reusable code objects built using annotated Java classes, XML, and resource bundles. They appear in the Process Modeler and can be used in your process models in the same manner as any other smart service.
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.
Tip: The bundled Plug-ins that are included in the product installer are contained in a file named appian-bundled-plugins.zip
.
See also: Appian Plug-ins
Creating and deploying the plug-inCopy link to clipboard
To create and deploy the smart service plug-in:
- Create a new Java project in Eclipse.
- Click File > New > Other….
- Select Java Project from the Select a wizard options. Click Next.
- Type a name for your project. Click Finish (accepting the default settings).
- Configure the Java Build Path.
- Right-click the project. Click Properties.
- In the left navigation, select Java Build Path.
- Select the Libraries tab. Click the Add External JARs… button.
- Add the following Appian JAR as an external dependency and click OK.
<APPIAN_HOME>/_admin/sdk/appian-plug-in-sdk.jar
- Your plug-in must be designed to access only the classes and methods documented in the Public API javadocs.
- Configure Project Folders.
- In the Package Explorer (left navigation) right-click the
src
folder. - Select New > Folder.
- Type
META-INF
in the folder name field and click Finish. - With the
META-INF
folder selected, right-click and select New > Folder. - Type
lib
in the folder name field and click Finish. - With the
src
folder selected, right-click and select New > Package. - Type your desired package structure in the name field.
For example: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
Copy
- In the Package Explorer (left navigation) right-click the
- Add the JAR files required by your smart service to the
src/META-INF/lib/
folder.- These JAR files are deployed by your plug-in.
- If your plug-in needs a resource included in an Appian JAR file, do not add that file to this folder. Instead, reference the Appian JAR from your build path.
- Any third party libraries that are specifically used by your plug-in must be included in the
src/META-INF/lib/
folder.
- Create your class and add it to the
src/com.example.plugins.<YOUR_PLUGIN>
package.- Right-click the project and select New > Class.
- Enter the package for the class (such as
com.example.pluginname
). - Enter the name of the class (the smart service name).
- In superclass, click Browse.
- Type
AppianSmartService
— OR — Typecom.appiancorp.suiteapi.process.framework.AppianSmartService
. - Ensure that the Inherited Abstract Methods checkbox is selected and click Finish.
- Note: Only use Appian's public Java API to invoke Appian functionality. Generally, public interfaces are found in
com.appiancorp.suiteapi
.
- Note: Only use Appian's public Java API to invoke Appian functionality. Generally, public interfaces are found in
- Update your Java Build Path to include any new JAR files; otherwise, Eclipse won't compile.
- Right-click the project and select Properties.
- In the Package Explorer (left navigation) click Java Build Path.
- On the Libraries tab, click Add JARs….
- Select the JAR files in your project.
- Register the smart service in an
appian-plugin.xml
file. See below: Configuring the appian-plugin.xml File - Add your appian-plugin.xml file at the root level.
- Add your internationalization bundles to the
src/com.example.plugins.<YOUR_PLUGIN>
package. See below: Internationalization. - Export your project as a JAR file.
- Right-click your project and click Export….
- Select the JAR file option as the Export destination.
- On the Resources to export dialog, clear the .classpath and .project selections as these files are used exclusively by Eclipse.
- Select the _admin/plugins folder of your installation directory for your export destination. This directory is created during application server startup.
- Click Finish.
Your plug-in is deployed. The plug-ins framework locates the new JAR file and deploys your custom smart service when the application server starts.
Java componentCopy link to clipboard
A custom Smart Service is built using a class that extends AppianSmartService.
- Annotations on the class and the methods within the class determine its behavior.
- Activity class parameters (node inputs) and Activity return variables are created using setter and getter methods of your class.
- Validation
- A helper class is not needed.
Plug-in classCopy link to clipboard
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.
- It must extend the class
com/appiancorp/suiteapi/process/framework/AppianSmartService.java
AppianSmartService, whoserun
method is overridden.- This method is where the main logic of the smart service resides.
- Annotations on the class can be used to define smart service properties and attributes.
- Methods within the class determine its behavior as a smart service.
Inputs and outputsCopy link to clipboard
Inputs (Activity Class Parameters) and Outputs (Activity Class Return Variables) are defined using public getter and setter methods within your class.
- For an input,
set<InputName>
methods returnvoid
and take a single parameter.- An an alternate, input or output names can be set using the
@Name
annotation. For example:@Name("NewTopic")
. Names should use camel case. (Capitalize each word and do not use spaces.)
- An an alternate, input or output names can be set using the
- If you do not define an internationalization key for an input or output, the display name of the input can be automatically derived from the
<InputName>
where each capital letter in your (camel case) getter or setter method name indicates a new word in the display name.- See below: Internationalization
- Node inputs must be defined using the correct Java data type for the data type that you need to use.
- See below: Defining Data Types for Inputs and Outputs.
- Appian types can be implied by passing a
Long
parameter and annotating the method with a type annotation.
- Overloaded methods are not supported.
- If two methods have the same name (but different signatures because they take different parameters), only the first encountered is available as an input or output.
- Input methods that take Java primitives must be marked as required.
- Passing a
null
value to a Java primitive is not possible. - If an input is optional, you must use a wrapper class (such as Boolean for boolean) to allow the input to accept nulls, and then handle that null scenario in the body of the method.
- Use the
Required.OPTIONAL
designation when upgrading custom smart services from earlier versions (before Appian 6.0.3) which used a value of2
for the required element in the activity class parameter schema. - See below: Validation
- Passing a
- For an output,
get<OutputName>
methods are used without an explicit setter.- If you define a corresponding setter method, the getter method is treated as an input.
- There is no annotation for outputs.
- When configuring node inputs and outputs ensure that the name given to these parameters are not identical to one another. The names of all inputs (Activity Class Parameters) and outputs (Activity Return Variables) in a smart service must be unique.
Defining input attributes and behaviorCopy link to clipboard
- @Input: (Optional) This annotation can be used with a setter method to define the attributes and behavior of the input. If not present, the following values are used, taking their default settings.
- required: (Optional) defines whether or not the input is required.
- The possible values are fields of the Required class:
com.appiancorp.suiteapi.process.framework.Required.ALWAYS
is the default setting. - A value of
OPTIONAL
allows the process designer to select whether a form element that maps to the input is required or not).
- The possible values are fields of the Required class:
- enumeration: (Optional) use this attribute if the value for the input should be from a list of acceptable values.
- This attribute is set to name of the enumeration as defined in the
enumerations
element in theappian-plugin.xml
. - See below: Enumerations
- This attribute is set to name of the enumeration as defined in the
- localId: (Optional) used for converting preexisting smart services to the annotated smart service plugins.
- It should be used when the target system already has models that use the existing smart service that is being replaced by the new plug-in.
- The value of the activity-class-parameter-schema local-id should be passed to this attribute.
- This attribute is not recommended for new smart service plug-ins, nor is it required.
- customDisplayReference: (Optional) If a picker used by the input should be something other than the standard picker for the type, the picker name should be given here.
- For text inputs, the htmlarea value can be given (to give a rich text editor like that in the Send Alert smart service).
- hiddenFromDesigner: (Optional) Should the input need to be hidden from the designer in the modeler, set this to
true
using a default value.- Unless a default value of true is set, this attribute is always false.
- defaultValue: (Optional) if the input should have a default value, this attribute is used to list a String representation of that default.
- Multiple default values are listed as an array of strings.
- The following example could be used to list a multiple Boolean default:
@Input(defaultValue={"false","true","true"})
- Values for custom data types must be provided in the
appian-plugin.xml
. - In addition to the string representation in the
defaultValue
attribute of the@Input
annotation, default values can be defined in XML in the smart-service element of theappian-plugin.xml
.
Defining data types used by inputs and outputsCopy link to clipboard
Your plug-ins can use complex and primitive system data types.
@Type
annotations can be used on getter and setter methods to indicate which Appian type they take or return.
Appian objects are represented by their long ID or in some cases by their string.
The following data types are deprecated and may be removed from Appian in a future release:
- DiscussionMessageDataType [Deprecated]
- DiscussionThreadDataType [Deprecated]
- ForumDataType [Deprecated]
- TaskDataType [Deprecated]
The following table lists available data type annotations for Appian data types.
Annotation | Appian Type | Definition |
---|---|---|
@ApplicationDataType | Application | @Type(namespace = Type.APPIAN_NAMESPACE, name="Application") |
@CommunityDataType | Community | @Type(namespace = Type.APPIAN_NAMESPACE, name="Community") |
@ConstantDataType | Constant | @Type(namespace = Type.APPIAN_NAMESPACE, name="Constant") |
@DocumentDataType | Document | @Type(namespace = Type.APPIAN_NAMESPACE, name="Document") |
@DocumentOrFolderDataType | Document or Folder | @Type(namespace = Type.APPIAN_NAMESPACE, name="ContentItem") |
@EmailAddressDataType | Email Address | @Type(namespace = Type.APPIAN_NAMESPACE, name="EmailAddress") |
@EmailRecipientDataType | Email Recipient | @Type(namespace = Type.APPIAN_NAMESPACE, name="EmailRecipient") |
@EncryptedTextDataType | Encrypted Text | @Type(namespace = Type.APPIAN_NAMESPACE, name="EncryptedText") |
@FolderDataType | Folder | @Type(namespace = Type.APPIAN_NAMESPACE, name="Folder") |
@GroupDataType | Group | @Type(namespace = Type.APPIAN_NAMESPACE, name="Group") |
@KnowledgeCenterDataType | Knowledge Center | @Type(namespace = Type.APPIAN_NAMESPACE, name="KnowledgeCenter") |
@PageDataType | Page | @Type(namespace = Type.APPIAN_NAMESPACE, name="Page") |
@PasswordDataType | Password | @Type(namespace = Type.APPIAN_NAMESPACE, name="Password") |
@ProcessModelDataType | Process Model | @Type(namespace = Type.APPIAN_NAMESPACE, name="ProcessModel") |
@RuleDataType | Rule | @Type(namespace = Type.APPIAN_NAMESPACE, name="Rule") |
@UserDataType | User | @Type(namespace = Type.APPIAN_NAMESPACE, name="User") |
@UserOrGroupDataType | User or Group | @Type(namespace = Type.APPIAN_NAMESPACE, name="UserOrGroup") |
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
Defining custom data types for inputs and outputsCopy link to clipboard
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") |
Defining Java objects for custom data typesCopy link to clipboard
Java objects can be used to create custom data types for use in your plug-ins.
See also: Custom Data Types from Java Object
Annotations on the classCopy link to clipboard
The following properties and attributes of a smart service activity can be defined using attributes within your Java class:
- Palette: Smart service activities appear as a flowchart tool in the left toolbar of the Process Modeler either within an existing palette, or within a new palette that you define.
- This property is set using the
@PaletteInfo
annotation. - Each activity is organized first into categories (folders), then into a palette (as a subcategory).
- You can select any existing category. Smart services should not be placed in the following palette subcategories:
- Human Tasks
- Activities
- Events
- Gateways
- This property is set using the
- Within each subcategory, activities appear alphabetically.
- Assignability: Smart service activities can be defined as assigned activities, automated activities, or this option can be left to the process designer.
- These settings are defined using the
@Attended
and@Unattended
annotations. - If you intend the activity to be assigned, set the activity to use a form.
- These settings are defined using the
- Order: The ordering in which your node inputs appear on the Data tab of your smart service properties dialog can be set using the
@Order
annotation.
@PaletteInfo annotationCopy link to clipboard
The following attributes of the @PaletteInfo
annotation define the palette placement for the smart service:
- paletteCategory: The Process Modeler palette category (folder) where the smart service appears; if it's not already present, it is created. If the category palette doesn't exist, it is mapped to Automation Smart Services. (This information is not internationalized.)
- palette: The palette (subcategory) where the smart service appears; if it's not already present, it is created. (This information is not internationalized.)
- A set of convenience annotations that extend
PaletteInfo
are defined for the standard smart service palette categories. The table below describes the standard palette annotations that you can use. - Use the following
paletteCategory
andpalette
values when you need to deprecate a custom smart service:@PaletteInfo(paletteCategory="#Deprecated#", palette="#Deprecated#")
. Deprecated services do 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) |
@Attended and @Unattended annotationsCopy link to clipboard
The @Attended
annotation is optional.
- When present, it requires that the activity be assigned, and is not permitted to run unattended.
The @Unattended
annotation is also optional.
- When present, the activity is configured to run unattended, and cannot be assigned.
If neither the @Attended
nor the @Unattended
annotations are present, the process designer can determine whether the activity is attended or not.
- This is also true if both annotations are present.
@Order annotationCopy link to clipboard
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 and outputsCopy link to clipboard
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.
Constructor parametersCopy link to clipboard
Instances of certain interfaces can be injected into the smart service by declaring constructor parameters of their type.
- The
SmartServiceContext
can be obtained by passing it in the constructor of the smart service class.- If you configure the activity to be executed without a form (as an automated activity) the process designer can select whether the activity executes with the user rights of the process designer, or the user rights of the process initiator. You can retrieve the user under whose context the smart service is running using the
getUsername
method inSmartServiceContext
.
- If you configure the activity to be executed without a form (as an automated activity) the process designer can select whether the activity executes with the user rights of the process designer, or the user rights of the process initiator. You can retrieve the user under whose context the smart service is running using the
- Any of the
*Service
interfaces in the public API (such as,ProcessAnalyticsService
, orContentService
) can be obtained by passing them as parameters to the constructor.- In order for the plug-in to be authorized to use the
EncryptionService
, the plug-in key must be granted access by an administrator in the Plug-ins page of the Admin Console. - You can only access the
EncryptionService
from within therun()
method, not from within your smart service's getters and setters. - Deprecated services, such as
com.appiancorp.suiteapi.collaboration.DocumentService
, are not injected. In place of the DocumentService, use thecom.appiancorp.suiteapi.content.ContentService
. - Note: This is the only valid method for obtaining a
*Service
within custom Smart Service code. Do not callServiceLocator.get*
.
- In order for the plug-in to be authorized to use the
- The
javax.naming.Context
can be used in the smart service by adding it as a constructor parameter.- Use the
lookup
method inContext
to get access to the objects that are registered in the JNDI tree, such as data sources.
- Use the
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.
Plug-in constructor exampleCopy link to clipboard
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;
}
Copy
Handling credentials securelyCopy link to clipboard
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:
- Create an entry for the external system credentials in the Third-Party Credentials page in the Admin Console.
- Note the system key that is generated based on the given name.
- Pass this value as the parameter to
SecureCredentialsStore.getSystemSecuredValues(String)
orSecureCredentialsStore.getUserSecuredValues(String)
to obtain the map of credentials.
- Pass this value as the parameter to
- Create credentials. The given field names translate into the attribute keys that are used in the map according to the following rules:
- The name is lower-cased.
- Any punctuation is stripped.
- Any spaces are translated into dots.
- Once the plug-in is deployed, add it to the list of plug-ins allowed to access the credentials by picking the plug-in in the Plug-ins List section on the Third-Party Credential page.
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:
Secure credentials store exampleCopy link to clipboard
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
}
}
Copy
ValidationCopy link to clipboard
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.
- Override the
validate
method to define your custom validation.- The method takes a
MessageContainer
as a parameter.
- The method takes a
- The
validate
method is called before therun
method for both attended and unattended activities.- For assigned activities, validation takes place after the user submits the task.
- When creating a custom validation, ensure that you do whatever validation is necessary to make sure that the run method is successful and populate the
MessageContainer
parameter with your error messages to be displayed to the user. - When validate adds to the
MessageContainer
parameter using theaddErrors
method, the form is not submitted and therun
method is not called.- The errors appear on the form for the user to correct before attempting to submit the form again.
- The
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.- You do not have to implement additional logic to save the values of inputs with the onSave method.
- That function is taken care of automatically.
- The
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. - If the
onSave
method adds any errors to theMessageContainer
parameter, the form is not saved and these messages appear on the form. - The
validate
andonSave
methods are not required.
Legacy smart service validationCopy link to clipboard
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.
Class exampleCopy link to clipboard
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();
}
}
Copy
See also: Process Upgrade
Configuration filesCopy link to clipboard
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.
Configuring the appian-plugin.xml fileCopy link to clipboard
Each plug-in requires that an appian-plugin.xml
file appear in the root (highest level) of a Plug-in.
- When this file is not present, the Plug-in is entirely ignored.
This file can use The following elements and attributes:
appian-plugin: (Required)
- For a smart service plug-in, this root element holds one
plugin-info
, one or moreenumeration
, and one or moresmart-service
child elements. - The element itself contains the plug-in name (name=) and plug-in key (key=) attributes.
key Attribute: (Required) A Unique identifier for each Plug-in.
- Bundled Plug-ins use a
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
- Vendor
- Version
- Application-version
description: (Optional)
-
List a description for the Plug-in. vendor: (Optional) A subelement of
plugin-info
. It holdsname
andurl
attributes. - Name example:
"Example Company"
- URL example:
"http://www.example.com"
version: (Required) A subelement of plugin-info
.
- Plug-in version example:
2.0.0
application-version: (Optional) A subelement of plugin-info
. It accepts the min
attribute, indicating a minimum product version.
- min example:
"6.0.2"
smart-service: (Required) A subelement of appian-plugin
that contains the following attributes and elements:
- name: (Optional attribute) The name of the smart service (as referred to in log entries) regarding the plug-in deployment.
- The display name in the modeler comes from the internationalization bundle, unless it is not defined there, in which case it uses this name. If a name is not defined, the class name is used, separating each camelCase part of the class name with spaces before each capital letter.
- key: (Required attribute) The identifier of the smart service, which must be unique in the system.
- class: (Required attribute) The class that implements the smart service.
- form: (Optional subelement) A default form can be defined for your smart service.
- local-id: (Optional attribute) Use when migrating a custom smart service built for a prior version of Appian.
- The value should be the same as the value of the
local-id
attribute of the legacy Appian 5.x custom smart service.
- The value should be the same as the value of the
activity-class-schema: (Deprecated Element)
- Smart service plug-ins no longer require that you define an
<activity-class-schema>
element for the smart service in yourappian-plugin.xml
file. Activity class schemas are available to support backwards compatibility. - See also: Appian 6.0.2 Custom Smart Service Plug-ins
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>
Copy
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.
- These elements nest as subelements of the
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>
Copy
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.
- Add one
<enumeration>
tag per enumeration being added by this plugin. - The enumerations added in a plugin are available throughout the system.
- Within the
appian-plugin.xml
, the enumeration declaration must appear before any smart service that uses it. - The
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>
Copy
Schema
An enumeration includes the following attributes and subelements:
- key: The key of the enumeration, which should be referenced when the enumeration is referenced by a smart service in an @Input annotation; in the example above, the annotation would appear as '@Input(enumeration="issue-severity")'.
- name: (optional) The name is an alias that can be used to reference the enumeration instead of using the key – which is included only for backwards compatibility.
- type: The type of the value, using the standard Appian data type ID numbers or
name{namespace}
- items/item: List one item element per option in the enumeration.
- label: The label displayed on the input list.
- detail: The label appears in a tooltip when resting your pointer on this item when it is displayed.
- value: The value that is used for an input when this item is selected. It must hold the same data type as the
enumeration/@type
annotation reference.- For Custom Data Types, an XML structure that represents the value can be used.
- The default value of an input is defined on the input, independent of any enumerations.
- desc and displayName attributes are not used.
Appian data type ID numbersCopy link to clipboard
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 |
Example plug-in file treeCopy link to clipboard
Smart service iconCopy link to clipboard
Each plug-in should include an icon to display in the Process Modeler along with the other smart services.
- If no icon is provided, the default icon is used.
- The icon should be sized to display in the Process Modeler palette (left toolbar) and the Process Modeler canvas.
- The following default image can be used as a palette icon template:
- The default palette icon is 27 pixels wide by 19 pixels high.
- Name the palette icon image
palette-icon.svg
. - The ALT tag for a palette icon is the smart service name. (Canvas icons do not display ALT tags.)
- The following default image can be used as a canvas icon template:
- The default canvas icon is 60 pixels wide by 40 pixels high.
- Name the canvas icon image
canvas-icon.svg
. - Store the images within your JAR file in the following path:
/{plug-in key}/{smart service key}/images/
.- For example, if the
plug-in key
iscom.example.pluginKey
, and the smart service key isyourKey
, store the images in thecom.example.pluginKey.yourKey
package, in a folder namedimages
.
- For example, if the
- Remove
<?xml version="1.0" encoding="UTF-8"?>
in the first line, or the SVG image will not display.
InternationalizationCopy link to clipboard
Each Plug-in must provide internationalization bundles for the US English locale (en_US). All other locales are optional.
- Plug-ins without a required resource bundle fail to deploy.
Resource bundle stringsCopy link to clipboard
The following items must have keys and text strings listed in your resource bundles:
- Node inputs (ACP) and output (ARV) display names and comments.
- Default Forms.
- Error messages.
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).
- For example,
/com/example/smart-services/trac-create-ticket_en_US.properties
Translation keysCopy link to clipboard
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.- If a name is not defined, the system falls back on the value of the name of the smart-service module.
input.<InputName>.displayName
: The name of the input displayed on the Inputs tabbed property sheet, on theData
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.- Input and output names must be unique, or deployment fails.
- If you do not define names for your inputs and outputs in your resource bundles, the display name is rendered automatically using the node input (ACP) name, with spaces separating the camel cased name: For example:
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.- If a comment is not defined, no tooltip is displayed.
InputName
andOutputName
are the camelCase names (or@Name
annotated names) of the targets of the getter and setter methods (after removing the prependedget-
orset-
).
Best practicesCopy link to clipboard
DevelopmentCopy link to clipboard
- All smart services should be shipped as plug-ins. All smart services built prior to the release of the Plug-in Architecture should be upgraded to the Plug-in Architecture.
- See also: Appian Plug-ins
- Use the appropriate annotations.
- See above: Annotations on the Class
- Inject Appian Services as Constructor Parameters instead of instantiating them manually.
- See above: Constructor Parameters
- Provide internationalization bundles for as many locales as possible - the US English locale (en_US) is required.
- See above: Internationalization
- Use enumerations when restricting inputs or outputs to a predefined list of values.
- See above: Enumerations
- Where possible, use Appian type inputs and outputs instead of passing Appian object IDs.
- Create and use custom types for inputs and outputs that require a nested structure.
- See above: Defining Custom Data Types for Inputs and Outputs and also Custom Data Types from Java Object
- Validate inputs for all attended custom smart services.
- See above: Validation
- Create new canvas and palette icons for all custom smart services.
- See above: Smart Service Icons
- If you need to modify a plug-in, but you anticipate some users will still need to run the previous version in active processes, deprecate the previous version and use either a different class name or different package name to avoid class conflicts when running both plug-ins on the same system.
- See above, steps 3 and 4: Creating and Deploying the Plug-in
UpgradingCopy link to clipboard
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. |
VersioningCopy link to clipboard
- Custom smart services use standard OSGi versioning: Later versions override earlier versions.
- Appian always uses the most current version of a smart service.
- If you upgrade a smart service, you must ensure that the smart service's behavior does not break preexisting code that relied on an older version.
- You cannot change the outputs or inputs of a deployed custom smart service.
- If you need to modify the inputs or outputs of a smart service, create a new smart service and deprecate the previous version rather than upgrading the existing one.
- Deprecated smart services are still available at runtime, but are hidden at design time.
- To avoid any class conflicts when using a deprecated plug-in on the same system as the new one, use a different class name or package name for the two versions.
Deployment issuesCopy link to clipboard
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. |
|
LimitationsCopy link to clipboard
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.
Appian Cloud customers ONLYCopy link to clipboard
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.
- An alternative is to utilize Appian professional services to build and test these custom smart services.
- Appian Technical Support is not responsible for any issue caused by custom smart services deployed on a site, Appian Cloud customers are responsible for maintaining these customizations.
- Once all the appropriate custom code is provided to Appian Technical Support, the custom node is available on the corresponding Appian Cloud site within a week.