Working with Modules
When you create a module, Studio generates a folder structure and a great many of the files you'll need to create a successful module project. For instructions on creating a new module, see the section titled Creating a New Module in this chapter.
The easiest way to view the file structure created by the New Module Wizard is to use the Navigator view in Studio.
Figure 3.2. The File Layout for a Module Project
Module Descriptor
The module descriptor contains the following basic information about a module: its prefix, ODN ID, version, name, description, author domain, and base package. It also includes a list of other modules it depends on. When you create a module, a module-descriptor.xml file is generated containing this information.
Below is the module-descriptor.xml file generated when you created the Bookstore module. The
odnId attribute will differ since it is generated by DevNetwork.
Module descriptors are not available programmatically, but you can retrieve all of the Module objects, and in turn their names and prefixes, at run-time using the ModuleService:
ModuleService moduleService = Services.get(ModuleService.
class
);
Set<Module> modules = moduleService.getModules();
for
(Module module : modules) {
String moduleName = module.getName();
String modulePrefix = module.getPrefix();
}
Model
A model is an XML based artifact from which other artifacts are generated including a plain old Java object (POJO) with persistence capabilities, audit trails, issue tracking, and extra field-level metadata. These artifacts are generated at build-time from the .model files created when you manipulate models in Studio, while the field metadata is extracted from the .model files and cached at run-time.
For more details on creating and working with models, please see the chapter titled Modeling.
Module-Defined Fields (MDFs)
MDFs provide a direct way to extend other models that may not otherwise fulfill a business case. The model framework allows module owners to add fields to the models of the modules they depend on. Like normal fields, MDFs will be reflected in the model's database table structure.
When you create MDFs in Studio, a ModelName.mdfs file is created in your models folder that contains the modifications to the model (note that the ModelName portion of the file name will start with the prefix of the module that owns the model). This file is used at build-time to regenerate the class files for the model and to add columns to the model's database table. After a build, the fields will be available for use programmatically. Assuming you added the String field ExtraField on the Alert model in the Bookstore tutorial, you could then manipulate the field:
Alert alert =
new
Alert();
com.mybooks.bookstore.model.AlertMDFs mdfs = alert.getMDFs(com.mybooks.bookstore.model.AlertMDFs.
class
);
mdfs.setExtraField(
"foo"
);
String extraField = mdfs.getExtraField();
Enumerations
Enumerations are a named set of string values. The enumeration definitions for a given module are kept in the .enums file in the models folder. At build-time, a Java enum is created for each definition; at run-time, the definitions are cached and loaded into the ENUMERATION table.
You can use enumerations as fields on a model by selecting the String Enumeration field type and selecting a corresponding enumeration. The field itself is stored on both the Java class and the database table as a string value, with no referential constraint on the ENUMERATION table, so conversion to a Java enum must be done manually using the get method on the enumeration class. For example, to manipulate the Genre field you created as part of the tutorial you could do the following:
Book book =
new
Book();
book.setGenre(GenreEnum.NON_FICTION.stringValue());
GenreEnum genre = GenreEnum.get(book.getGenre());
The stringValue method will return the value you specified when you created the enumeration value entry.
Module Process Template (MPT)
The MPT is the at the heart of your module project.
Figure 3.3. MPT File in Navigator View
The MPT files contain module-level metadata for each model. The actions, views, issues, workflows, callbacks, state machines, and permissions for your models are stored as part of the MPT. In addition, the MPT contains the integration interface definitions.
When you double click the MPT file in Studio, you gain access to all the features of the Platform SDK Plug-in. These features are covered extensively in the chapter titled Modeling.
A great deal of the Platform SDK's capabilities are built using metadata. For example, simple reports are created using XML files. Any changes to the MPT that only involve metadata can be dynamically reloaded on the server. This includes modifying actions, views, or permissions. You may also add new actions, but the associated constants will not be available until you have rebuilt and restarted the server. Any other changes that require modifications or additions to Java class files, such as adding a new workflow will also require a rebuild and restart.
The MPT itself is a collection of all of the .modelDef files in the Module Process Template.mpt.files folder. Each model that has extra meta-data (actions, views, etc.) defined will have a corresponding modelDef file.
Source code
The source code for your module resides in the src folder. There is also a test-src which is designed to contain unit tests.
Platform also generates source files using the modelDef, model, and mdfs files. These files are under build/gen-src and build/regen-dao. Many of these are used internally by Platform, but the classes under the model, enums, and mpt packages will be useful in developing your module. The model and enums packages contain the models and enumerations you created in Studio respectively. The mpt package contains constants for your module and constants for the actions, views, issues and states of the models.
Do not manually change the files under the build directory. Those changes will be lost when the module is rebuilt.
DDL
The ddl and ddl/drop directories contain custom SQL scripts that will run when building and dropping the module's schema, respectively. You can use these scripts to provide custom constraints or triggers that fall outside the scope of Studio.
jaxb.inj (Code Injections)
The underpinnings for Studio's POJO generation is based on a technology called Java Architecture for XML Binding (JAXB).
The jaxb.inj folder contains code injections for your model files. You can inject code into a model at build-time by including a ModelName.code file, replacing ModelName with the actual model name. This allows you to add code to the generated models without giving up all the advantages those generated models afford.
The following example for Book.code will result in a Book class that imports com.mybooks.bookstore.Media, implements the com.mybooks.bookstore.Media interface, and overrides the toString method:
<JaxbInjection>
<imports>
import
com.mybooks.bookstore.Media;
</imports>
<
implements
>Media</
implements
>
<code>
@Override
public
String toString() {
return
getTitle() +
", by "
+ getAuthor();
}
</code>
</JaxbInjection>
You cannot override a method that explicitly exists in the model class. For example, if Book.java already had a toString, adding it in the Book.code file would add another toString method in Book.java, resulting in a compile-time error.
lib
The lib directory contains any third-party libraries you wish to include at build- and run-time.
native
Native libraries will automatically be added to java.library.path based on the current OS.
Put your libraries in:
native/win32 for Windows 32-bit
native/win64 for Windows 64-bit
native/linux32 or simply native/linux for Linux 32-bit
native/linux64 for Linux 64-bit
There are two caveats to be aware of:
Make sure that no System.loadLibrary() calls happen in any class static initializers before the classpath has been modified properly. (Classpath modification happens in server bootstrap, so this is unlikely to be an issue.)
You will need to explicitly handle cases when one DLL(s) depends upon another.
For example, if dll_A depends on dll_B, you will receive a "dependent libraries not found" if you access dll_A outright. (This is because dll_A doesn't know about java.library.path at all when it loads its dependencies, so dll_B won't load). Thus the solution here is to modify your ModuleContextListener (or some similar location) to do something like this:
System.loadLibrary(
"dll_B"
);System.loadLibrary(
"dll_A"
);
Reports
The reports directory contains the custom SQL-based reports. See the chapter titled Reports for more details.
Web resources
The web directory contains web resources, including JavaScript, HTML, and internationalization labels.
During the build, the JavaScript and CSS files are combined into a single ModuleName.js and ModuleName.css file. The build.jsb file under public/scripts determines which JavaScript and CSS files are included in the combined files. This sample build.jsb does not create a CSS file and includes only the SampleData.js:
{
projectName:
'ZBKS js and css Build'
,deployDir:
'build'
,licenseText:
"scripts and css"
,pkgs: [{
name:
"Components"
,file:
"scripts/jsbuild/ZBKS.js"
,fileIncludes: [{
path:
'scripts/'
,text:
'SampleData.js'
}]
}]
,resources: []
}
The file entries should not be modified as these are used internally by the build process
For deploying JavaScript files, alternative technique is also available which is preferred over build.jsb technique. In this technique, the require.js framework is used for loading scripts dynamically. The JavaScript files are saved under public/jsmodules. The content of each of these files should start with a "define" function call in require js.
define(function() {
Ext.ns(
'ZBKS.search'
);
ZBKS.search.InvoiceSearch = Ext.extend(Ext.Component, {
...
});
});
The javascript files under public/jsmodules should be placed according to js class name. In the above example, a js class is created with name ZBKS.search.InvoiceSearch. The javascript file for this class need to be placed as public/jsmodules/search/InvoiceSearch.js. Here search is sub-directory and InvoiceSearch.js is a javascript file containing above javascript snippet.
XSD
The xsd directory contains custom XML Schema Definition (XSD) files that will be included in your module's XML schema. In addition, the build process will generate JAXB classes for the types you define. These are especially useful for classes that do not need database persistence but will be marshalled/unmarshalled to/from XML sources.
The ModuleInstanceConfig.xsd defines your module's InstanceConfig element. You can extend the existing definition to include elements that allow you to configure stage-level options, those that must be the same across all nodes. The following example modifies the ZBKSInstanceConfig used in the Bookstore to add optional Name and Type elements:
<xs:element name=
"ZBKSInstanceConfig"
type=
"ZBKSInstanceConfig"
substitutionGroup=
"vc:ModuleInstanceConfig"
/>
<xs:complexType name=
"ZBKSInstanceConfig"
>
<xs:complexContent>
<xs:extension base=
"vc:ModuleInstanceConfig"
>
<xs:sequence>
<xs:element name=
"Name"
type=
"xs:string"
minOccurs=
"0"
/>
<xs:element name=
"Type"
type=
"xs:string"
minOccurs=
"0"
/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
You can then modify your InstanceConfig.xml to include the element:
<ZBKSInstanceConfig xmlns=
"http://www.mybooks.com/Bookstore"
xmlns:vc=
"http://www.onenetwork.com/Platform"
>
<Name>Sample Name</Name>
<Type>Sample Type</Type>
</ZBKSInstanceConfig>
You can define defaults or use parameter replacement for your InstanceConfig element using the ModuleInstanceConfig.xml.template file. Parameters in the InstanceConfig are marked by <%parameter.name%>:
<ZBKSInstanceConfig xmlns=
"http://www.mybooks.com/Bookstore"
xmlns:vc=
"http://www.onenetwork.com/Platform"
>
<Name>Sample Name</Name>
<Type>Sample Type</Type>
</ZBKSInstanceConfig>
build.xml
The build.xml is an Ant build file that controls your module's build process. Please see the section in this chapter titled "Working with Ant" for more information on Ant and the build process.