Difference between revisions of "HowTo:Provide system definitions with a custom plugin"
m (→Prerequisites) |
|||
(7 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | + | This tutorial explains how to provide any kind of preconfigured {{UBIK}} system definitions using a custom plugin (or more specific: module). | |
− | + | ||
− | This tutorial explains how to provide any kind of preconfigured {{UBIK}} | + | |
== General concept == | == General concept == | ||
Line 9: | Line 7: | ||
{{UBIK}} plugins are detected when an Environment is initialized, and the user is prompted with a database upgrade if necessary. The technical maintenance of the database is automatic and allows for arbitrary adaptations. | {{UBIK}} plugins are detected when an Environment is initialized, and the user is prompted with a database upgrade if necessary. The technical maintenance of the database is automatic and allows for arbitrary adaptations. | ||
− | The plugin developer can decide whether to make meta definitions immutable or further customizable by the user. Custom scripts can be executed | + | The plugin developer can decide whether to make meta definitions immutable (as system design objects in the System namespace) or further customizable by the user (as regular content). Custom scripts can be executed every time the Environment is connected or just during an upgrade. |
== Prerequisites == | == Prerequisites == | ||
− | To understand the code, knowledge about the {{UBIK}} ER-model and the plugin injection mechanism is required. | + | To understand the code, knowledge about the {{UBIK}} [[Entity_Data_Model|ER-model]] and the [[Injection_Management|plugin injection mechanism]] is required. |
− | + | ||
− | + | ||
− | + | ||
== Instructions == | == Instructions == | ||
Line 24: | Line 19: | ||
= Big Picture = | = Big Picture = | ||
− | The | + | The Module implementation hosts a System Definition Provider, which in turn gives access to a set of versioned System Definitions. The latter can contain any kind of {{UBIK}} data like MetaClasses, MetaProperties, instances of any type and scripts to execute when the package is installed or initialized. |
[[File:Systemdefinitions2.drawio.png]] | [[File:Systemdefinitions2.drawio.png]] | ||
Line 297: | Line 292: | ||
public class MyMetaProperties | public class MyMetaProperties | ||
{ | { | ||
− | public const string NS_MY_PLUGIN = "System.MyPlugin; | + | public const string NS_MY_PLUGIN = "System.MyPlugin"; |
public static readonly Guid PROPERTY_UID_MYPROPERTY = Guid.Parse("d90b50e0-17d9-40ac-b89d-18f4a01cd5cb"); | public static readonly Guid PROPERTY_UID_MYPROPERTY = Guid.Parse("d90b50e0-17d9-40ac-b89d-18f4a01cd5cb"); | ||
Line 713: | Line 708: | ||
#endregion System Meta Properties | #endregion System Meta Properties | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | = Create a system classification = | ||
+ | |||
+ | Here's an example for the definition of a system classification. | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">SystemClassification template code</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MyClassification : SystemClassification | ||
+ | { | ||
+ | public static Guid GUID = new Guid("aa9cc5cb-f676-4b60-b39d-eab7a8bcc66e"); | ||
+ | |||
+ | public MyClassification(UBIKEnvironment environment) | ||
+ | : base(GUID, environment) | ||
+ | { | ||
+ | this.CodedName = "MYCLS"; | ||
+ | this.CodedNamespace = "System.MyPlugin"; | ||
+ | this.CodedDescription = "My classification"; | ||
+ | |||
+ | // Derives from SystemDesignMetaClass, see above for metaproperty definition | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">SystemDefinitions adaptations</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MySystemDefinitions_V100 : SystemDefinitionsBase | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | #region System Classifications | ||
+ | |||
+ | public override UBIKClassList<MetaClass> DefineSystemClassifications(UBIKEnvironment environment) | ||
+ | { | ||
+ | return new UBIKClassList<MetaClass>() | ||
+ | { | ||
+ | new MyClassification(environment) | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | #endregion System Classifications | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | = Create a system SelectiveList = | ||
+ | |||
+ | Here's an example for the definition of a system selective list. | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">SystemDefinitions adaptations</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MySystemDefinitions_V100 : SystemDefinitionsBase | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | #region System Selective Lists | ||
+ | |||
+ | public override UBIKClassList<SelectiveList> DefineSystemSelectiveLists(UBIKEnvironment environment) | ||
+ | { | ||
+ | UBIKClassList<SelectiveList> selectiveLists = new UBIKClassList<SelectiveList>(); | ||
+ | |||
+ | // MyEnumType should be an integer enum | ||
+ | SystemSelectiveList fromEnum = new SystemSelectiveList(environment, typeof(MyEnumType)); | ||
+ | fromEnum.SetUID(GuidUtility.SLFROMENUM); | ||
+ | selectiveLists.Add(fromEnum); | ||
+ | |||
+ | SystemSelectiveList customList = new SystemSelectiveList(environment); | ||
+ | customList.SetItemValueType(PropertyTypes.String); | ||
+ | customList.AddSelectiveItem("any object of supported type", "description"); | ||
+ | customList.Name = "SL_STR"; | ||
+ | customList.SetUID(GuidUtility.SLSTR); | ||
+ | selectiveLists.Add(customList); | ||
+ | |||
+ | return selectiveLists; | ||
+ | } | ||
+ | |||
+ | #endregion System Selective Lists | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | = Perform pre- or postprocessing scripts = | ||
+ | |||
+ | In some cases, one has to perform an action before the database is adjusted, or postprocess the upgrade. | ||
+ | It is also possible to execute code whenever the environment is initialized, even if no upgrade was necessary. | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">Scripting template code</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MySystemDefinitions_V100 : SystemDefinitionsBase | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | public override void PreProcessDatabaseUpdate(Version previousVersion, UBIKEnvironment environment) | ||
+ | { | ||
+ | base.PreProcessDatabaseUpdate(previousVersion, environment); | ||
+ | |||
+ | // This code will be executed whenever the system is upgraded, | ||
+ | // before a schema upgrade for this SystemDefinitions package | ||
+ | } | ||
+ | |||
+ | public override void PostProcessDatabaseUpdate(Version previousVersion, UBIKEnvironment environment) | ||
+ | { | ||
+ | base.PostProcessDatabaseUpdate(previousVersion, environment); | ||
+ | |||
+ | // This code will be executed whenever the system is upgraded, | ||
+ | // after a schema upgrade and before content initialization and upgrade | ||
+ | // for this SystemDefinitions package | ||
+ | } | ||
+ | |||
+ | public override void InitializeSystemContent(UBIKEnvironment env) | ||
+ | { | ||
+ | base.InitializeSystemContent(env); | ||
+ | |||
+ | // This code will be executed whenever the system is initialized, | ||
+ | // after an optional schema upgrade and before an optional content adaptation | ||
+ | // for this SystemDefinitions package | ||
+ | } | ||
+ | |||
+ | public override void UpdateSystemContent(UBIKEnvironment env) | ||
+ | { | ||
+ | base.UpdateSystemContent(env); | ||
+ | |||
+ | // This code will be executed whenever the system is upgraded, | ||
+ | // after all other steps for this SystemDefinitions package | ||
+ | } | ||
+ | |||
+ | // ... | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | = Create a custom InterfaceAdmin = | ||
+ | |||
+ | This is one of the most complex things you can do in this topic. | ||
+ | That's because an InterfaceAdmin object has the following interesting aspects: | ||
+ | * It is an instance of the INTERFACE_ADMIN (Meta)MetaClass | ||
+ | * It is a MetaClass itself | ||
+ | * A custom implementation must derive from the INTERFACE_ADMIN_ROOT | ||
+ | * It produces special instances called InterfaceExecution | ||
+ | * Any functional InterfaceAdmin runtime object must be an instance of InterfaceAdministration | ||
+ | |||
+ | The last point is crucial, because this makes our task difficult. | ||
+ | After all, we use SystemDesignMetaClass to define MetaClasses, and this directly conflicts with the last requirement. | ||
+ | The way to solve this is to deliver a non-functional class we only use as a basis, and then derive a regular object from it. | ||
+ | |||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">InterfaceAdmin template code</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MyInterfaceAdminBase : SystemDesignMetaClass | ||
+ | { | ||
+ | public static new Guid UID = GuidUtility.INTERFACE_ADMIN_UID_BASE; | ||
+ | private const string NAME = "BASE_IFA"; | ||
+ | private const string NAMESPACE = "System.MyPlugin.Interface.Administration"; | ||
+ | private const string DESCRIPTION = "Base Class of Custom Interface Admin"; | ||
+ | |||
+ | |||
+ | public MyInterfaceAdminBase(UBIKEnvironment environment) | ||
+ | { | ||
+ | InitUidAndEnvironment(UID, environment); | ||
+ | InitStrings(NAME, NAMESPACE, DESCRIPTION); | ||
+ | |||
+ | SetModuleId(MyPlugin.ID); | ||
+ | |||
+ | List<Guid> metaProperties = new List<Guid>(); | ||
+ | DefineMetaProperties(metaProperties); | ||
+ | } | ||
+ | |||
+ | public override MetaClass MetaClass | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | // The current class is an instance of the Interface Admin MetaClass; an individual Interface Administration object. | ||
+ | return this.Environment.GetSystemMetaClass(SystemObjects.INTERFACE_ADMINISTRATION); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public override MetaClass Inheritance | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | // The current class derives from the default Interface Admin, which is also the MetaClass of all Interface Execution objects. | ||
+ | // Likewise, the current class is a MetaClass for Interface Execution objects. | ||
+ | return this.Environment.GetSystemMetaClass(SystemObjects.INTERFACE_EXECUTION); | ||
+ | } | ||
+ | set | ||
+ | { | ||
+ | throw new NotImplementedException(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public override MetaClassTypes ClassType | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return MetaClassTypes.DEFINED_IN_PLUGIN; | ||
+ | } | ||
+ | set | ||
+ | { | ||
+ | // do nothing | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public override string RuntimeTypeName => GetRuntimeType().FullName; | ||
+ | |||
+ | public override string SystemRuntimeTypeName => GetSystemRuntimeType().FullName; | ||
+ | |||
+ | public override Type GetSystemRuntimeType() | ||
+ | { | ||
+ | return typeof(MyInterfaceExecution); | ||
+ | } | ||
+ | |||
+ | public override Type GetRuntimeType() | ||
+ | { | ||
+ | return typeof(MyInterfaceExecution); | ||
+ | } | ||
+ | |||
+ | // Required for MetaClasses that have a very special MetaClass themselves | ||
+ | protected override MetaClass DeliverMetaMetaClassInstance() | ||
+ | { | ||
+ | return MetaClass; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">InterfaceExecution runtime type template code</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin.InterfaceExecutors | ||
+ | { | ||
+ | public class MyInterfaceExecution : InterfaceExecution | ||
+ | { | ||
+ | protected override IInterfaceExecutor GetExternalDataReader() | ||
+ | { | ||
+ | return new ThirdPartySystemWebAPIClientReader(Environment); | ||
+ | } | ||
+ | |||
+ | protected override IInterfaceExecutor GetExternalDataWriter() | ||
+ | { | ||
+ | return new ThirdPartySystemWebAPIClientWriter(Environment); | ||
+ | } | ||
+ | |||
+ | protected override IInterfaceExecutor GetExternalPollingExecutor() | ||
+ | { | ||
+ | return new ThirdPartySystemWebAPIClientPoller(Environment); | ||
+ | } | ||
+ | |||
+ | protected override IInterfaceExecutor GetExportExecutor() | ||
+ | { | ||
+ | return new MyExportExecutor(Environment); | ||
+ | } | ||
+ | |||
+ | protected override IInterfaceExecutor GetImportExecutor() | ||
+ | { | ||
+ | return new MyImportExecutor(Environment); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | </div></div> | ||
+ | |||
+ | <div class="toccolours mw-collapsible mw-collapsed" style="width:100%; overflow:auto;"> | ||
+ | <div style="font-weight:bold;line-height:1.6;">SystemDefinitions adaptations</div> | ||
+ | <div class="mw-collapsible-content"> | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | namespace UBIK.MyPlugin | ||
+ | { | ||
+ | public class MySystemDefinitions_V100 : SystemDefinitionsBase | ||
+ | { | ||
+ | // ... | ||
+ | |||
+ | #region System Content | ||
+ | |||
+ | public override void UpdateSystemContent(UBIKEnvironment environment) | ||
+ | { | ||
+ | base.UpdateSystemContent(environment); | ||
+ | |||
+ | // Create our own custom interface admin from the base class we described. | ||
+ | MetaClass mc = environment.GetSystemMetaClass(SystemObjects.INTERFACE_ADMINISTRATION); | ||
+ | InterfaceAdministration ifa = environment.UBIKDataFactory().GetBaseObject(mc, GuidUtility.INTERFACE_ADMIN_UID_CUSTOM) as InterfaceAdministration; | ||
+ | if (ifa == null) | ||
+ | { | ||
+ | ifa = (InterfaceAdministration)environment.UBIKDataFactory().MetaClass(GuidUtility.INTERFACE_ADMIN_UID_BASE).CreateNewDerivate(GuidUtility.INTERFACE_ADMIN_UID_CUSTOM); | ||
+ | ifa.Name = "MY_IFA"; | ||
+ | ifa.Description = "Custom Interface Admin"; | ||
+ | ifa.Namespace = "Custom.MyPlugin.Interface.Administration"; | ||
+ | ifa.ClassType = MetaClassTypes.Inherited; | ||
+ | ifa.Save(); | ||
+ | } | ||
+ | |||
+ | Relationship relation = environment.GetSystemRelation(SystemObjects.RELATION_INTERFACE_ADMIN_METAPROXY); | ||
+ | MetaProxy target = (MetaProxy)environment.UBIKDataFactory().MetaClass(GuidUtility.MY_PROXY); | ||
+ | if(ifa.IsAddRelationAllowed(relation, target)) | ||
+ | { | ||
+ | ifa.AddRelation(relation, target); | ||
+ | ifa.Save(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | #endregion System Content | ||
// ... | // ... | ||
Line 725: | Line 1,056: | ||
* [[HowTo:Create_UBIK_Module|Create your own module]] | * [[HowTo:Create_UBIK_Module|Create your own module]] | ||
− | + | * [[Injection_Management]] | |
− | [[ | + | * [[Plugins]] |
− | [[ | + | |
[[Category:How-To|Provide system definitions with a custom plugin]] | [[Category:How-To|Provide system definitions with a custom plugin]] | ||
+ | [[Category:Injecting|Provide system definitions with a custom plugin]] | ||
[[Category:Module|Provide system definitions with a custom plugin]] | [[Category:Module|Provide system definitions with a custom plugin]] | ||
+ | [[Category:Plugin|Provide system definitions with a custom plugin]] |
Latest revision as of 11:09, 28 February 2025
This tutorial explains how to provide any kind of preconfigured UBIK® system definitions using a custom plugin (or more specific: module).
General concept
When rolling out a UBIK® customizing into a (productive) environment, there is always the challenge regarding the transportation of the data model and basic data. The sustainable solution is to deliver versioned packages of system definitions in plugins, because that allows for easy deployment and upgrade, and for the direct application of state-of-the-art software development strategies for plugin development.
UBIK® plugins are detected when an Environment is initialized, and the user is prompted with a database upgrade if necessary. The technical maintenance of the database is automatic and allows for arbitrary adaptations. The plugin developer can decide whether to make meta definitions immutable (as system design objects in the System namespace) or further customizable by the user (as regular content). Custom scripts can be executed every time the Environment is connected or just during an upgrade.
Prerequisites
To understand the code, knowledge about the UBIK® ER-model and the plugin injection mechanism is required.