Skip to content

TG Consultancy Guidelines

Rowdy edited this page Jun 4, 2019 · 120 revisions

TG Consultancy Guidelines

Contents

  1. Abstract
  2. Prerequisites
  3. Configuration of Eclipse
  4. Install code completion template into Eclipse
  5. Install code formatting template into Eclipse
  6. Configure Eclipse save actions
  7. Registering a new JRE
  8. Setupping Maven
  9. Checkout/update tg-app-archetype
  10. Creating a new TG-based application from the archetype
  11. Importing a new project into Eclipse
  12. Initially retrieving a project from GitHub via command line
  13. Initially retrieving a project from GitHub not via command line
  14. Updating an existing project from GitHub via command line
  15. Updating an existing project from GitHub not via command line
  16. Committing changes to GitHub via command line
  17. Committing changes to GitHub not via command line
  18. Eclipse keyboard shortcuts
  19. Useful TG functions or shortcuts
  20. Adding a property to a class
  21. Adding a unit test
  22. Displaying a newly added property in the UI
  23. Adding a new package
  24. Creating a new Master entity (class)
  25. Creating a new One-2-Many entity (class)
  26. Adding a validator
  27. Adding an after change handler
  28. Using the EQL (Entity Query Language)
  29. Auto-generating a key
  30. Constructing a meaningful warning message
  31. Commit message format
  32. Naming classes, properties and methods
  33. Upon the use of AbstractEntity.isPersisted()
  34. Upon the use of AbstractEntity.isInitialising()
  35. Upon the use of BigDecimal
  36. Upon the use of tgprop-persistently-calculated
  37. Upon the use of @Dependent
  38. Upon the use of StringValidator
  39. Solving a stack overflow from Maven
  40. How to create customised icon in TG
  41. Troubleshooting
  42. TODO

Abstract

This document provides guidelines for performing TG-related consultancy activities.


Prerequisites

  • Java 8 (preferably the latest update)
  • Eclipse 4.6.3 (Neon) or later
  • An Eclipse workspace directory that does not have a full stop in it
  • Maven 3.3 or later
  • FMS.oc mvn account on boomer (contact ML to arrange)
  • LaTeX (on Microsoft OS, TexLive is suggested, on OS X MacTex is suggested)
  • Git client (GitHub client is recommended by those who like fluffy clicky GUIs for Windows and OS X, everyone else prefers the command line tools)
  • GitHub.com account (self-signup, contact ML or Rowdy to add you to FMS company account)
  • ssh key registered at FMS.eu (to access FMS.eu Git repositories) (contact 0H to arrange) (only required to use the archetype)
  • ssh key registered with GitHub (see https://help.github.com/articles/generating-ssh-keys/ for details)
  • TG plugin for Eclipse (place into eclipse/dropins directory and restart Eclipse) Note that TG Eclipse plugin is available from GitHub tg-tools project (although ML usually makes a binary copy available)
  • A copy of the latest edition of The Book i.e. "TG or !TG - The Developer's Guide"

Eclipse plugins are installed via "Help - Install New Software..." except for the TG Eclipse plugin, which should be copied into the dropins directory under the Eclipse application directory.

All packages listed above should be on the path.

All packages listed above should be appropriately configured.


Configuration of Eclipse

Install code completion template into Eclipse

  • Select: Windows - Preferences
  • Filter by: templates
  • Select: Java - Editor - Templates
  • Select all existing tg* templates and click Remove (otherwise they will duplicate)
  • Click Import ...
  • Browse to and select tg-templates.xml and click OK
  • Click Apply
  • This should add the tg* templates (seen in the above image)

Note that the latest templates can be found in the TG platform source, in directory ~/git/TG/platform-doc/eclipse-templates

Install code formatting template into Eclipse

  • Select Window - Preferences
  • Filter by: formatter
  • Select: Java - Code Style - Formatter
  • Click Import...
  • Browse to and select code-formatting-template.xml and click OK
  • Click Apply
  • This should introduce "standard" Java code formatting

Note that the latest templates can be found in the TG platform source, in directory ~/git/TG/platform-doc/eclipse-templates

Configure Eclipse save actions

(actions performed when a source file is saved)

  • Select: Windows - Preferences
  • Filter by: action
  • Select Java - Editor - Save Actions
  • Enable save actions
  • Format source code
  • Format all lines (although some prefer only edited lines, to reduce the number of unrelated changes to a source file, however as everyone is supposed to be using the same code formatting styles, this should not be an issue)
  • Organise imports
  • Additional items
  • Click Configure... (and continue below)

Remove trailing whitespace on all lines and correct indentation.

Use modifier "final" where possible.

Registering a new JRE

Select Window - Preferences from the main menu, then select Java - Installed JREs from the preferences treeview. Click the "Add..." button to register a new JRE and select the JRE path by clicking the "Directory..." button in the above dialog. The rest of the fields will be filled by default based on the selected JRE. Click the Finish button to finish.

It is suggested to have one workspace for Java 7 projects, and to have the respective JDK 7 checked in the above form. It is suggested to have a separate workspace for Java 8 projects, and to have that respective JDK 8 checked.

Setupping Maven

Obtain settings.xml (and maybe settings.xml.au and settings.xml.ua) from somewhere e.g. an existing installation, and copy them into the .m2 directory in your home directory.


Checkout/update tg-app-archetype

This only needs to be done prior to generating a new TG-based application from the archetype, and then only if the archetype is absent or changed since the last update.

  1. Start a command prompt or shell.

  2. Navigate to the Eclipse workspace directory.

  3. If tg-app-archetype is not already checkouted:

    1. Issue command:

      git clone ssh://gitolite@www.fielden.com.ua:10022/tg-app-archetype

      Ensure that the command has finished successfully.

  4. If tg-app-archetype is already checkouted:

    1. Change to tg-app-archetype directory within the Eclipse workspace.

    2. Issue command:

      git pull

      Ensure that the command has finished successfully.

  5. Change into the tg-app-archetype directory (if not already) and issue command:

    mvn install

    Ensure that the command has finished successfully.


Creating a new TG-based application from the archetype

Prerequisite: Ensure that the tg-app-archetype project is already checkouted and/or updated, and installed.

  1. Start a command prompt or shell.

  2. Navigate to the Eclipse workspace directory.

  3. Issue command:

    mvn archetype:generate -Dfilter=fielden:

    1. Archetype:

      Choose archetype:
      1: local -> fielden:tg-application-archetype (This is a project template for constructing TG-based information systems.
      It provides templates for all application modules as per Trident Genesis platform specification.
      The development teams should use this template to significantly automate the initial creation of TG-based applications.)
      Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): :
      

    Here there is only one archetype, select it by typing "1".

    1. Group id:

      Define value for property 'groupId': :
      

      This should be a short name without any non-alpha characters, e.g. "fielden".

    2. Artifact id:

      Define value for property 'artifactId': :
      

      This is basically the (short) name of the project, without special characters e.g. "coolapp".

    3. Version:

      Define value for property 'version':  1.0-SNAPSHOT: :
      

      Press enter to confirm the default.

    4. Package:

      Define value for property 'package':  fielden: :
      

      The Java package name, often the same as the company name. Press Enter to accept the default.

    5. Company name:

      Define value for property 'companyName': :
      

      The full name of the company e.g. "Fielden Management Services".

    6. Platform version:

      Define value for property 'platformVersion': :
      

      The version of the TG platform that should be used. Currently "1.2-SNAPSHOT".

    7. Project name:

      Define value for property 'projectName': :
      

      The full name of the project e.g. "Cool App"

    8. Project web site:

      Define value for property 'projectWebSite': :
      

      The project's web site. The site does not have to exist, but must be a full and valid URL e.g. http://www.fielden.com.au/coolapp

    9. Support email:

      Define value for property 'supportEmail': :
      

      The project's support email address. The address does not have to exist, but must be a full and valid email address e.g. coolapp_support@fielden.com.au

  4. Maven will summarise the parameters, and prompt for final confirmation. Press Enter to accept the default "Y" to confirm, or type "N" to go through the prompts again.

  5. Ensure that the app was created successfully. There should also be a directory in the Eclipse workspace corresponding to the app's short name e.g. coolapp.


Importing a new project into Eclipse

Prerequisite: A new app has been created from the archetype.

  1. Start a command prompt or shell.

  2. Navigate to the Eclipse workspace directory, and then the project directory within the Eclipse workspace e.g. /workspace/coolapp

  3. Issue command:

    mvn eclipse:clean

    Ensure that the command has finished successfully.

  4. Issue command:

    mvn -DdownloadSources="true" -DdownloadJavadocs="true" eclipse:eclipse

    Ensure that the command has finished successfully.

  5. Start Eclipse pointing to the workspace used for the above actions.

  6. Import the project using the steps outlined in The Book section 2.3.2 "Importing Project into Eclipse". It is recommended to also setup a working set during project import for convenience.

  7. Run PopulateDb to create an initial consultancy database:

    1. Switch to the Java perspective in Eclipse.

    2. Press Ctrl-Shift-T to invoke the Open Type dialog.

    3. Type PopulateDb in the filter field at the top of the Open Type dialog.

    1. Click OK.

    2. Press Ctrl-F11 (in the Eclipse editor) to run PopulateDb.

  8. Create a run configuration for the server. Follow the instructions in The Book section 2.3.3 "Setting up Run Configurations". Note that Program arguments should be:

    application.properties
    

    and VM arguments should be:

    -Djava.system.class.loader=ua.com.fielden.platform.classloader.TgSystemClassLoader
    -XX:+UseConcMarkSweepGC
    

    Note that the second line -XX:+UseConcMarkSweepGC is more relevant for deployment environments rather than consultancy environments.

  9. Create a run configuration for the client. Follow the instructions in The Book section 2.3.3 "Setting up Run Configurations". Note that Program arguments should be:

    application.properties
    

    and VM arguments should be:

    -Dswing.defaultlaf=javax.swing.plaf.nimbus.NimbusLookAndFeel
    -Dswing.systemlaf=javax.swing.plaf.nimbus.NimbusLookAndFeel
    -splash:target/classes/images/splash.jpg
    -Djava.system.class.loader=ua.com.fielden.platform.classloader.TgSystemClassLoader
    -Dawt.useSystemAAFontSettings=on
    

Initially retrieving a project from GitHub via command line

  1. Start a command prompt or shell.

  2. Navigate to the git working directory e.g. ~/git

  3. Issue command:

    git clone git@github.com:fieldenms/TTGAMS.git

    Note that the repository name (TTGAMS) may differ, depending on, which project you are cloning.

    Ensure that the command has finished successfully. There should be a new subdirectory called TTGAMS.

  4. Navigate to the new project directory e.g. ~/git/TTGAMS and then the source directory inside that (e.g. ttgams), in, which directory should be pom.xml

  5. Issue command:

    mvn -DdownloadSources="true" -DdownloadJavadocs="true" eclipse:eclipse

    Ensure that the command has finished successfully.

  6. Start Eclipse and import the new project/s into Eclipse.

  7. Switch to the Java perspective in Eclipse before performing any further consultancy.


Initially retrieving a project from GitHub not via command line

  1. See Myroslav.

Updating an existing project from GitHub via command line

  1. Start a command prompt or shell.

  2. If the TG platform source is present:

    1. Navigate to the TG platform source directory.

    2. Issue command:

    git pull

    Ensure that the command has finished successfully.

    1. Ensure the Java perspective is selected in Eclipse.

    2. Select all Eclipse projects corresponding to the TG platform and press F5 to refresh.

    3. Select Project -�� Clean, ensure the projects selected in the treeview are selected in the checkbox list, and click OK.

  3. For the project itself (e.g. TTGAMS):

    1. Navigate to the project'��s git working directory e.g. ~/git/TTGAMS

    2. Issue command:

    git pull

    Ensure that the command has finished successfully.

    1. Ensure the Java perspective is selected in Eclipse.

    2. Select all Eclipse projects corresponding to the Git project and press F5 to refresh.

    3. Select Project - Clean, ensure the projects selected in the treeview are selected in the checkbox list, and click OK.

    4. If there are compilation errors:

      • Navigate to the project directory within the project's git working directory e.g. if the project's git working directory is ~/git/TTGAMS and then the project directory inside that should be something like ~/git/TTGAMS/ttgams

      • Issue command:

      mvn clean

      Ensure that the command has finished successfully.

      • Issue command:

      mvn eclipse:clean

      Ensure that the command has finished successfully.

      • Issue command:

      mvn -DdownloadSources="true" -DdownloadJavadocs="true" eclipse:eclipse

      Ensure that the command has finished successfully.

      • Repeat step 3 sub-steps c, d and e.

      If errors persist, they will need to be corrected before proceeding.


Updating an existing project from GitHub not via command line

  1. See Myroslav.

Committing changes to GitHub via command line

  1. Start a command prompt or shell.

  2. Navigate to the project directory inside the git working directory e.g. ~/git/TTGAMS

  3. Issue command:

    git status -s

    This should produce output in the general format:

     M ttgams/ttgams-dao/src/test/java/fielden/work/validation/WorkActivityStatusAfterChangeTest.java
     M ttgams/ttgams-pojo-bl/src/main/java/fielden/work/common/WorkActivityStatusRequiredProperty.java
    ?? ttgams/ttgams-web-client/bin/
    ?? ttgams/ttgams-web-server/bin/
    

    Lines beginning with M identify modified files, lines beginning with ?? identify new files.

  4. If any new files were added that need to be committed, add each one with a command like:

    git add <filename>
    
  5. Issue the following command to commit the changes locally:

    git commit -m "commit message goes here"
    

    Ensure that the command has finished successfully.

  6. Issue the following command to push the recently committed changes to the upstream repository:

    git push
    

    Ensure that the command has finished successfully.


Committing changes to GitHub not via command line

  1. See Myroslav.

Eclipse keyboard shortcuts

Shortcut (Microsoft) SHortcut (Mac) What Why
Ctrl-space Ctrl-space Content assist. Displays a dropdown list, where possible, of things that can be typed at the current insertion point location.
Ctrl-1 ⌘-1 Quick fix. When invoked while the insertion point is within an error, displays a list of suggestions to correct the situation.
Ctrl-A ⌘-A Select all. Selects all text in the current editor window (select all in other contexts is nowhere near as useful).
Ctrl-I ⌘-I Reformat the selected text. Reformats the selected text, usually to replace tabs with spaces and thus correct all the indentation artefacts. Often used after Ctrl-A.
Ctrl-M Ctrl-M Maximise/restore selected window. To see more code, or to see all windows/panels.
Ctrl-O ⌘-O Outline. Displays an outline of the current source file (properties and methods) - useful for quickly finding a method definition within a source file.
Ctrl-W ⌘-W Close current tabsheet. Close the current tabsheet, including closing a file and conditionally prompting to save changes if it is an editor tabsheet.
Ctrl-F11 Run unit tests. Why do you reckon?
Ctrl-Page Up Ctrl-Page Up Move to previous editor tabsheet. Navigation between open files.
Ctrl-Page Down Ctrl-Page Down Move to next editor tabsheet. Navigation between open files.
Shift-Alt-R Refactor. Rename a class, method or property and automatically update all references to it.
Shift-Ctrl-F Format source. Reformats all source lines in accordance with the current source formatting options.
Shift-Ctrl-O Optimise imports. When adding cod, Eclipse cannot or does not always automatically add the require import statements. This forces it to do so.
Shift-Ctrl-T Shift-⌘-T Open type. Invokes the Open Type dialog, from, which a type (class) can be located.
Shift-Ctrl-X Shift-⌘-X Uppercase selected text. Converts the selected text to uppercase.
Shift-Ctrl-Y Shift-⌘-Y Lowercase selected text. Converts the selected text to lowercase.

Useful TG functions or shortcuts

Shortcut What
getUser() Returns the currently loginned user (as type User).
@SessionRequired A decorator that specifies that the method should execute within a database transaction.
@TransactionDate Defaults to today'��s date when a new entity is persisted.

Adding a property to a class

This is covered in more detail in The Book.

  1. Ensure the Java perspective is selected in Eclipse.

  2. Locate the class in application-pojo-bl.

  3. Move the insertion point to just after the last property definition.

  4. Type "tgp" (without quotes) and press Ctrl-space. Something like the following list of options should be shown:

  5. For an ordinary property, select tgprop-ordinary from the list of code completion suggestions (, which are obtained from Java code templates imported previously). Note that the other property types should be selected where applicable.

  6. Complete the required details by typing in the field place-holders and using Tab to move between them.

  7. Run PopulateDb to add the new column to the table in the database.

  8. If the property is boolean, then change the getter'��s name from getSomething() to isSomething(). This small change makes ghoti elsewhere more readable.


Adding a unit test

This is covered in more detail in The Book.

  1. Switch to the Java perspective in Eclipse.

  2. With Shift-Ctrl-T locate the respective unit test class for the entity e.g. for Person the unit test class is called (slightly inconsistently) PersonnelTest, so the Open Type filter could be PersonT. Select the one that is most appropriate for the project you are working on (bearing in mind that there could be several similar, or almost identical, files in several different TG-based projects in the workspace).

    Note also that unit tests are in the -dao module.

  3. Perhaps copy/paste an existing test, change the method name to something explicitly relevant to what is being tested.

    Unit tests should be named as explicitly and verbosely specific as possible, all in lower case, and with underscores separating words in the name. For example:

    Good unit test names:

    • should_populate_closed_by_and_closed_date_when_closed_is_set_to_true()
    • actual_duration_for_wa_without_actual_start_should_be_null()
    • actual_duration_for_wa_with_actual_start_and_no_repair_finish_should_be_null()

    Bad unit test names:

    • checkBinQuantityOnHandAndUnitPricePopulation()
    • populate_closed_by()
    • test_work_order_status()

    Terrible unit test names:

    • test1()
    • test_something()
    • testSomethingElse()
  4. Implement the test.

  5. Run the test - at this stage (of the test-driven consultancy process) the test is expected to undergo upgrade.

  6. Enhance the business rules to implement the necessary conditions.

  7. Run the test again - if all goes well the test should pass.


Displaying a newly added property in the UI

This is covered in more detail in The Book.

  1. Switch to the Java perspective in Eclipse.

  2. With Shift-Ctrl-T locate the respective main view class e.g. searching for the main view class for person you could type PMV (PersonMainView) in the Open Type filter field. Then select the one that is most appropriate for the project you are working on (bearing in mind that there could be several similar, or almost identical, files in several different TG-based projects in the workspace).

  3. Locate in the source where "row 1" and "row 2" (and perhaps other rows) are already defined.

  4. Insert code to add the new field in an appropriate position.


Adding a new package

Typically done when about to create a new class.

  1. Switch to the Java perspective in Eclipse.

  2. Locate the -pojo-bl branch in the Package Explorer and expand the tree to show the parent package.

  3. Right click on the parent package and select New -> Package from the popup menu.

  4. Specify the new class name, noting that a full stop can be used to create multiple levels of package hierarchy, and that a convention is sometimes to prefix the package name with "fielden".

    Note that packages names are generally always lower case.

  5. Click Finish and the new package should be created and selected. Note the different icon as the package is initially empty.


Creating a new Master entity (class)

This is covered in more detail in The Book.

  1. Switch to the Java perspective in Eclipse.

  2. Select and expand pojo-bl in the Package Explorer in Eclipse.

  3. Locate the parent package in the Package Explorer, right-click on it and select TG Model Actions -> Create master Entity/Companion Objects.

    Note that this is the simplest case, typically used for a simple entity with a single base-type key (such as String or Integer).

  4. Specify a name for the new entity, noting that camel case with an initial upper is conventional.

    Inspect and adjust, if required, the checkboxes in the lower part of the dialog to specify whether a description is required, whether the entity should be persistent (saved in the database), whether mixin should be used, and whether deletion should be supported.

    Note that the Superclass is always ua.com.fielden.platform.entity.AbstractEntity<String> and that this can be changed in the generated source after it is generated, for example if the key is really Integer.

  5. After clicking the Finish button in the above dialog, the following reminder dialog is displayed.

    It is recommended to do this action now, to avoid forgetting later on.

  6. Press Shift-Ctrl-T to invoke the Open Type dialog. Specify "ApplicationD" as the type name prefix. Select the ApplicationDomain that is relevant to the project you are working on and click OK.

  7. In the ApplicationDomain.java source, add a line similar to the existing lines, but replace the class name with the name of the class that has just been added.

    Note that this will be flagged as an error initially, but if "optimise imports" is enabled then this will be automatically resolved when the file is saved.


Creating a new One-2-Many entity (class)

This is covered in more detail in The Book.

  1. Refer to and follow all of the steps in section "Creating a new Master entity (class)", except you should select "Create One-2-Many Entity/Companion Objects..." from the TG Model Actions sub-menu.

  2. "One-2-Many" will create, essentially, a multi-segment key with multiple entity members. To create one of these, select the class corresponding to the first segment of the key, and TG will automatically use that selected existing entity as the first segment. Then you only need to add additional entities to the key as required.

  3. @KeyTitle in the new entity should be changed, typically to the same as the entity title. For example:

    @KeyTitle("Inventory Supplier")

  4. The associated mixin would be generated with a syntax error in findDetails() method - orderBy() will have a reference to undefined "property_name" - this needs to be replaced with actual property name, typically @CompositeKeyMember(2).

    1. A new (artificial) One-2-Many entity WorkActivityPerson was created, with the intention of having WorkActivity as the first key member, and Person as the second.

    1. The mixin (in package fielden.work.mixin) contained an error, as Eclipse did not know what the second key member would be when the mixin was created.

    1. The string "property_name" was replaced with the actual property name corresponding to the second segment of the key.


Adding a validator

  1. Switch to the Java perspective in Eclipse.

  2. Locate the class and property to, which a validator is to be added.

  3. Insert a line like the one below starting with @BeforeChange:

    @IsProperty
    @MapTo
    @Title("Parent WA")
    @BeforeChange(@Handler(ParentWorkActivityValidator.class))
    private WorkActivity parentWorkActivity;
  4. ParentWorkActivityValidator.class will initially be flagged as an error. Position the insertion point on it and press Ctrl-1 and select "Create class ..." from the popup menu.

    Note that if this menu option does not appear (sometimes only the "Rename in file" option appears), close the source file and reopen it. This is believed to be a feature of Eclipse. If it still does not appear, try inserting a space before the closing parenthesis, save the file and do the Ctrl-1 again. If it still does not appear, try closing and reopening Eclipse. If it still does not appear, keep trying the above solution as in most cases at least one of these eventually results in success.

  5. The New (class) dialog will appear:

  6. The package into, which the validator is placed should be the same package as the base class with ".validation" appended e.g. fielden.work.validation

  7. Click the Add... button next to Interfaces and select IBeforeChangeEventHandler. Click OK.

  8. Back in the New (class) dialog, change the <T> in the class added to the Interfaces section to <xxx>, where xxx is the same class as the base property to, which the validator applies e.g. WorkActivity

  9. Click the Finish button in the New (class) dialog. The new class source should be displayed in a new editor tab.

  10. Move the insertion point to the class name (e.g. ParentWorkActivityValidator) and press Ctrl-1. Select "Add unimplemented methods" from the popup menu.

  11. The final validator class should be ready for the actual validation to be implemented.


Adding an after change handler

  1. Switch to the Java perspective in Eclipse.

  2. Locate the class and property to, which an after change handler is to be added.

  3. Insert a line like the one below starting with @AfterChange:

    @IsProperty
    @MapTo
    @Title("High Risk")
    @AfterChange(EscCocHighRiskDefiner.class)
    private boolean highRisk = false;
  4. EscCocHighRiskDefiner.class will initially be flagged as an error. Position the insertion point on it and press ⌘-1 and select "Create class ..." from the popup menu.

  5. The New (class) dialog will appear.

  6. The package into, which the after change handler is placed should be the same package as the base class with ".definers" appended e.g. fielden.work.definers

  7. Click the Add... button next to Interfaces and select IAfterChangeEventHandler. Click OK.

  8. Back in the New (class) dialog, change the <T> in the class added to the Interfaces section to <xxx>, where xxx is the same class as the base property to, which the validator applies e.g. Boolean.

  9. Click the Finish button in the New (class) dialog. The new class source should be displayed in a new editor tab.

  10. Move the insertion point to the class name (e.g. EscCocHighRiskDefiner) and press ⌘-1. Select "Add unimplemented methods" from the popup menu.

  11. The final after change class should be ready for the actual after change ghoti to be implemented.

    package fielden.work.definers;
    
    import ua.com.fielden.platform.entity.meta.IAfterChangeEventHandler;
    import ua.com.fielden.platform.entity.meta.MetaProperty;
    
    public class EscCocHighRiskDefiner implements IAfterChangeEventHandler<Boolean> {
    
        @Override
        public void handle(MetaProperty<Boolean> property, Boolean entityPropertyValue) {
            // TODO Auto-generated method stub
        }
    }

Using the EQL (Entity Query Language)

  1. Switch to the Java perspective in Eclipse.

  2. Ensure the project is up to date and built without errors.

  3. Select the source file in, which it is desired to utilise EQL.

  4. Type "tgq" and press Ctrl-space. For this example select tgquery-entity from the autocompleter list.

    This will insert cod as follows:

    final EntityResultQueryModel<type> query = select(type.class).model();
    final fetch<type> fetch = fetch(type.class).with("prop1");
    final OrderingModel orderBy = orderBy().prop("prop1").asc().model();
    final QueryExecutionModel<type, EntityResultQueryModel<type>> qem = from(query).with(fetch).with(orderBy).model();
  5. Complete the ghoti by specifying the "type" and "prop1" property names. Here is an example that was prepared earlier:

    final EntityResultQueryModel<WorkActivityStatusRequiredProperty> propQuery = select(WorkActivityStatusRequiredProperty.class).where().prop("workActivityStatus").eq().val(workActivityStatus).model();
    final fetch<WorkActivityStatusRequiredProperty> propFetch = fetch(WorkActivityStatusRequiredProperty.class).with("waTypes");
    final OrderingModel propOrderBy = orderBy().prop("requiredProperty").asc().model();
    final QueryExecutionModel<WorkActivityStatusRequiredProperty, EntityResultQueryModel<WorkActivityStatusRequiredProperty>> propQem = from(propQuery).with(propFetch).with(propOrderBy).model();
    final List<WorkActivityStatusRequiredProperty> propList = coWorkActivityStatusRequiredProperty.getAllEntities(propQem);

    Note that this example has been enhanced slightly further, with less generic names applied to the variables, a filter added to the fetch, and the data actually retrieved into a List.

  6. For the sake of completeness, the ghoti parts are as follows:

    • EntityResultQueryModel<type> query - specifies, which entities are to be retrieved, and the selection criteria used to select them
    • fetch<type> fetch - the fetch model, whether to fetch base types only, whether to include entities, whether to include child entities, whether to include specific properties
    • OrderingModel orderBy - how to sort the results
    • QueryExecutionModel<type, EntityResultQueryModel<type>> qem - puts all of the above items together, but note that it still does not actually retrieve anything
  7. In order to actually retrieve the data, you then need to do something like this:

    final List<WorkActivityStatusRequiredProperty> propList = coWorkActivityStatusRequiredProperty.getAllEntities(propQem);

    This will use the QueryExecutionModel that was prepared earlier to actually retrieve data, returning it in a List. Note that a companion object is required.

  8. In order to process the data, you can iterate through the list, with a construct created as follows:

    Just under where the List is created, type "foreach" and press Ctrl-space. From the auto-completer dropdown select "foreach - iterate over an array or iterable".

    By virtue of having placed the foreach immediately after creating a List, Eclipse is intelligent enough to auto-populate the for loop with the appropriate variables, such as:

    //Load required properties for each of collected statuses.
    final fetch<WorkActivityStatusRequiredProperty> propFetch = fetchAll(WorkActivityStatusRequiredProperty.class).with("waStatusTypeXrefs");
    final OrderingModel propOrderBy = orderBy().prop("requiredProperty").asc().model();
    final QueryExecutionModel<WorkActivityStatusRequiredProperty, EntityResultQueryModel<WorkActivityStatusRequiredProperty>> propQem = from(propQuery).with(propFetch).with(propOrderBy).model();
    final List<WorkActivityStatusRequiredProperty> propList = coWorkActivityStatusRequiredProperty.getAllEntities(propQem);
    
    for (final WorkActivityStatusRequiredProperty waProp : propList) {
    
    }

Auto-generating a key

  1. Switch to the Java perspective in Eclipse.

  2. Ensure the project is up to date and built without errors.

  3. Decide, which class would benefit from an auto-generated sequential key. Open the corresponding DAO. For example the Stocktake class would benefit from an auto-generated batch number, so open the StocktakeDao.java file.

  4. Override the save() method (if not already overridden, add to it if it is already overridden), and create something like the following ghoti:

    @Override
    @SessionRequired
    public Stocktake save(final Stocktake entity) {
        if (!entity.isPersisted() && (entity.getKey() == null)) {
            entity.setKey(keyGen.nextNumber("ST"));
        }
        return super.save(entity);
    }

    @Override is required to override the method in the base class.

    @SessionRequired is required for database transaction control.

    !entity.isPersisted() is true if the entity has not yet been saved to the database.

    entity.getKey() if null, needs to be generated.

    entity.setKey(keyGen.nextNumber("ST")); generates the next number from the "ST" sequence and assigns it to the entity's key.

  5. In PopulateDb add something like the following, to set an initial value for the "generator" (note that the comment serves to remind what that particular "generator" is used for):

    save(new_(KeyNumber.class, "ST").setValue("0")); // used by Stocktake key generator

Constructing a meaningful warning message

Warning messages issued to the user should be reasonably verbose, relevant to what the user was trying to do, and contain as much identifying information as possible. To facilitate the creation of warning messages, there are several facilities available.

Consider this example from ConversionFactorValidator:

@Override
public Result handle(final MetaProperty property, final BigDecimal newValue, final BigDecimal oldValue, final Set<Annotation> mutatorAnnotations) {
    final String cfTitle = StringUtils.capitalize(property.getTitle().toLowerCase());

    if (newValue.compareTo(BigDecimal.ZERO) < 1) {
        return Result.failure(newValue, cfTitle + " must be geater than 0.");
    }

    final InventoryPart inventoryPart = (InventoryPart) property.getEntity();
    if ((inventoryPart.getUnitOfMeasure() != null) && (inventoryPart.getUnitOfPurchase() != null)) {
        if (inventoryPart.getUnitOfMeasure().equals(inventoryPart.getUnitOfPurchase()) && (!BigDecimal.ONE.equals(newValue))) {
            final String uomTitle = inventoryPart.getProperty("unitOfMeasure").getTitle().toLowerCase();
            final String uopTitle = inventoryPart.getProperty("unitOfPurchase").getTitle().toLowerCase();
            return Result.failure(newValue, cfTitle + " must be 1 if the " + uomTitle + " and " + uopTitle + " are the same.");
        }
    }

    return Result.successful(newValue);
}

cfTitle is set to the @Title value for the specific property. In this instance the property is declared as:

@IsProperty
@MapTo
@Required
@Title(value = "Conversion Factor", desc = "Ratio of unit of purchase to unit of measure. Normaly more than 1.")
@BeforeChange(@Handler(ConversionFactorValidator.class))
private BigDecimal conversionFactor = BigDecimal.ONE;

So cfTitle would be set to "Conversion Factor". This is lowercased and capitalised to make a more syntactically accurate sentence.

Similarly uomTitle is set to:

inventoryPart.getProperty("unitOfMeasure").getTitle().toLowerCase()

unitOfMeasure is defined as:

@IsProperty
@MapTo
@Required
@Title("Unit of Measure")
@AfterChange(UnitOfMeasureDefiner.class)
@Dependent("conversionFactor")
private UnitOfMeasure unitOfMeasure;

So its title is "Unit of Measure". As for cfTitle, the unit of measure title is lowercased, but as it is appearing in the middle of a warning message, it is not capitalised.

The actual error message that is returned would look like:

"Conversion factor must be 1 if the unit of measure and unit of purchase are the same."

This is quite meaningful, should exactly correspond to the field labels that the user can see on the form, and explains what should be done to correct the situation.

Java.lang.String.format

Furthermore it has been suggested to use String.format to construct messages in TTGAMS. If imports are not automatically organised, it may be necessary to:

import static java.lang.String.format;

Messages can then be constructed like:

final String message = format("bla-bla %s bla-bla %s", value1, value2);


Commit message format

When committing changes into GitHub (or wherever the ghoti is stored), you are usually required to provide a commit message. The commit message (for GitHub in particular) should appear as follows:

#issue comment describing the changes in relatively precise detail

#issue should refer to the GitHub issue number that is being addressed by the commit.

When an issue is resolved by whatever is being committed, the text "closes" can be added in front of the #issue number (separated from the #issue number by a space). This will trigger GitHub to automatically close the issue.


Naming classes, properties and methods

In general, and apparently in accordance with a Java convention, CamelCase with an initial uppercase character is used to name classes (entities).

Similarly, camelCase with an initial lowercase character is used to name properties and methods.

Unit tests are named with_very_long_meaningful_names_in_lowercase_with_underscores_between_words.

For a name that is a short uppercase abbreviation such as DFA (Department of Foreign Affairs), it is better to convert it to lowercase: dfa, getDfa and setDfa look better than dFA, getDFA and setDFA.

Getters and setters should only differ from the property name by the case of the first letter e.g. property costCentre should have getter getCostCentre() and setter setCostCentre(). Having something subtly different like getCostCenter() and/or setCostCenter() will only lead to confusion, disorientation and all sorts of unexpected errors.

Date properties should be named somethingDate, not dateSomething (e.g. transactionDate, not dateTransaction).


Upon the use of AbstractEntity.isPersisted()

AbstractEntity.isPersisted() is useful in validators (i.e. @BeforeChange handlers) and definers (i.e. @AfterChange handlers) to determine if an entity has been saved (persisted) already, and thus apply different logic depending on whether it has or has not been persisted. This bears some similarity to testing whether DataSet.State = dsInsert in Delphi.

InventoryBinValidator is a good example of validator - any value is accepted for an Inventory, which was not yet persisted, but only declared bins are accepted for persisted Inventory.

public class InventoryBinValidator implements IBeforeChangeEventHandler<InventoryBin> {

    @Override
    public Result handle(final MetaProperty property, final InventoryBin newValue, final InventoryBin oldValue, final Set<Annotation> mutatorAnnotations) {
        final Inventory inventory = (Inventory) property.getEntity();
        final String binTitle = property.getTitle().toLowerCase();
        final String inventoryTitle = TitlesDescsGetter.getEntityTitleAndDesc(Inventory.class).getKey().toLowerCase();
        if (inventory.isPersisted()) {
            if (newValue.isPersisted() && inventory.equals(newValue.getInventory())) {
                return Result.successful(newValue);
            } else {
                return Result.failure(newValue, "The new " + binTitle + " '" + newValue.getBin() + "' should be declared against this " + inventoryTitle + " first.");
            }
        } else {
            return Result.successful(newValue);
        }
    }
}

WorkActivityTypeDefiner is a good example of definer - default values are populated for WA fields depending on WA Type only for a work activity, which was not yet persisted.

public class WorkActivityTypeDefiner implements IAfterChangeEventHandler<WorkActivityType> {
    @Override
    public void handle(final MetaProperty property, final WorkActivityType waType) {
        final WorkActivity wa = (WorkActivity) property.getEntity();
        if (!wa.isPersisted() && (wa.getStatus() == null)) {
            wa.setStatus(waType.getDefaultWaStatus());
        }

        //lets populate default values for WA fields depending on WA Type and WaTypeDefault
        if (!wa.isPersisted()) { //is in insert mode
            <snip ghoti>
        }
    }
}

AbstractEntity.isPersisted() is also useful in companion Dao objects - refer WorkActivityDao, InventorySupplierDao etc.

public class WorkActivityDao extends CommonEntityDao<WorkActivity> implements IWorkActivity {
    @Override
    @SessionRequired
    public WorkActivity save(final WorkActivity entity) {
        if (!entity.isPersisted() && (entity.getKey() == null)) {
            entity.setKey(keyGen.nextNumber("WA"));
        }
        <snip ghoti>
    }
}

Upon the use of AbstractEntity.isInitialising()

AbstractEntity.isInitialising() is useful in definers: while validators are not executed when an entity is instantiated from persistent storage, definers are. AbstractEntity.isInitialising() is used to apply special logic in such situations.

WorkActivityStatusDefiner is a good example - a different query is constructed if workActivity.isInitialising().

public class WorkActivityStatusDefiner implements IAfterChangeEventHandler<WorkActivityStatus> {
    @Override
    public void handle(final MetaProperty property, final WorkActivityStatus newStatus) {
        final WorkActivity workActivity = (WorkActivity) property.getEntity();

        // This handler should behave differently during initialisation state and during normal execution
        final EntityResultQueryModel<WorkActivityStatusRequiredProperty> propQuery;
        if (workActivity.isInitialising()) {
            // 1. Ignore the new status, create a select model to obtain all statuses from status change history
            //    of the specified WA
            final EntityResultQueryModel<WorkActivityStatus> auditSelect =
                    select(WorkActivityStatusAudit.class).
                    where().prop("workActivity").eq().val(workActivity).
                    yield().prop("newWaStatus").modelAsEntity(WorkActivityStatus.class);
            // 2. Use the above select to obtain all required properties for all historical statuses
            propQuery = select(WorkActivityStatusRequiredProperty.class).
                    where().prop("workActivityStatus").in().model(auditSelect).model();
        } else {
            // Create a select model to obtain all required properties for new status
            propQuery = select(WorkActivityStatusRequiredProperty.class).where().
                               prop("workActivityStatus").eq().val(newStatus).model();
        }

        <ghoti snipped>
    }
}

Upon the use of BigDecimal

For floating point numbers, the use of the BigDecimal type is recommended. However when initialising a BigDecimal, do not use a floating point argument, as this could experience ruonding artifacts, resulting in a BigDecimal with a less than optimal level of precision.

For example, instead of:

final BigDecimal one = new BigDecimal(1.1);

, which produces 1.100000000000000088817841970012523233890533447265625, you should use a string argument like this:

final BigDecimal one = new BigDecimal("1.1");

, which produces exactly 1.1.

Also note the constants BigDecimal.ZERO, BigDecimal.ONE and BigDecimal.TEN, which may further alleviate any potential rounding issues.

It is also recommended to explicitly specify the precision and scale for BigDecmial properties, for example:

@IsProperty
@Readonly
@MapTo(precision = 18, scale = 2)
@Title(value = "Converted Hours", desc = "T1 hours + 1.5 * T1.5 hours + 2 * T2 hours")
private BigDecimal convertedHours;

Upon the use of tgprop-persistently-calculated

The shortcut tgprop-persistently-calculated can be used when adding a new property to en antity, where the property needs to be calculated, and to be stored in the database. Such properties are usually read-only i.e. cannot be changed by the user.

This will insert ghoti similar to the following (, which will need to be fulfilled wrt title, description, type and name):

@IsProperty
@Readonly
@MapTo
@Title(value = "Title", desc = "Desc")
private type name;

@Observable
protected Timesheet setName(final type name) {
    this.name = name;
    return this;
}

public type getName() {
    return name;
}

// TODO - call this method from every setter, which affects this property
private void recalculateName() {
    // TODO - implement recalculation logic 
    setName(recalculatedValue);
}

An example of an actual property is as follows:

    @IsProperty
    @Readonly
    @MapTo(precision = 18, scale = 2)
    @Title(value = "Converted Hours", desc = "T1 hours + 1.5 * T1.5 hours + 2 * T2 hours")
    private BigDecimal convertedHours;

    private void recalculateConvertedHours() {
        BigDecimal recalculatedValue = BigDecimal.ZERO;
        final BigDecimal onePointFive = new BigDecimal("1.5");
        final BigDecimal two = new BigDecimal("2");

        if (t1Job != null) {
            recalculatedValue = recalculatedValue.add(t1Job);
        }
        if (t1Travel != null) {
            recalculatedValue = recalculatedValue.add(t1Travel);
        }
        if (t15Job != null) {
            recalculatedValue = recalculatedValue.add(t15Job.multiply(onePointFive)).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
        if (t15Travel != null) {
            recalculatedValue = recalculatedValue.add(t15Travel.multiply(onePointFive)).setScale(2, BigDecimal.ROUND_HALF_UP);
        }
        if (t2Job != null) {
            recalculatedValue = recalculatedValue.add(t2Job.multiply(two));
        }
        if (t2Travel != null) {
            recalculatedValue = recalculatedValue.add(t2Travel.multiply(two));
        }
        setConvertedHours(recalculatedValue);
    }

    @Observable
    protected Timesheet setConvertedHours(final BigDecimal convertedHours) {
        this.convertedHours = convertedHours;
        return this;
    }

    public BigDecimal getConvertedHours() {
        return convertedHours;
    }

This is called from various related setters, where setting a particular value would affect the calculation, such as in the following examples:

    @Observable
    public Timesheet setT1Travel(final BigDecimal t1Travel) {
        this.t1Travel = t1Travel;
        recalculateActualHours();
        recalculateConvertedHours();
        return this;
    }

    @Observable
    public Timesheet setT1Job(final BigDecimal t1Job) {
        this.t1Job = t1Job;
        recalculateActualHours();
        recalculateConvertedHours();
        return this;
    }

In the above examples, changing the value of t1Travel or t1Job would affect the calculated value of convertedHours, thus the recalculateConvertedHours() method is called. Note in that example that method recalculateActualHours() is also called, suggesting the altering the value of t1Travel or t1Job affects more than one calculated property.


Upon the use of @Dependent

Many property validators depend on other properties. This means that when the other property changes, this property needs to be re-validated.

In TG this is supported through @Dependent annotation.

It is suspected that due to the learning nature of TTGAMS development, @Dependent annotation may not have been applied everywhere where it is appropriate.

To determine if @Dependent is applicable, search for property.getEntity() and then check if the entity is used to get other properties - most likely these other properties need to have @Dependent added with the reference to the property being validated.

For example, from class Timesheet:

    @IsProperty
    @Title("Craft/Employee")
    @MapTo
    @BeforeChange({ @Handler(ActivatablePropertyValidator.class), @Handler(TimesheetPersonValidator.class) })
    @CompositeKeyMember(1)
    @Dependent(value = { "nightrateHours", "standbyHours", "phoneFix", "shiftLeave" })
    private Person person;

This indicates that the properties nightrateHours, standbyHours, phoneFix and shiftLeave are dependent on person. If property person changes, then the validation methods for the four specified fields are automatically triggered.


Upon the use of StringValidator

StringValidator is a validator for strings. Using it, strings can be validated to ensure that they are valid strings.

For example, to validate a string with a regular expression to ensure that the contents of the string represent a valid email address:

    @IsProperty
    @MapTo
    @Title(value = "Email", desc = "Desc")
    @BeforeChange(@Handler(value = StringValidator.class, str = {@StrParam(name = "regex", value = ".+\\@.+\\..+")}))
    private String email;

Solving a stack overflow from Maven

It has been observed that Maven might cause a stack overflow when performing mvn clean install.

A work-around is to set an environment variable as follows:

$ export MAVEN_OPTS="-Xms256m -Xmx1024m -Xss1024k"

The values can be tweaked to suit the consultancy environment, then the mvn clean install command repeated.


How to create customised icon in TG

  1. Create customised SVG image for your icon. Inkscape or any other software could be used to do this. Provide it with id (id="sequential-edit") by including it into the <svg> tag.

    Example: SequentialEdit.svg was created and located into ttgams-web-ui\src\main\resources\images_editing\

  2. Optimise the SVG code after exporting it from the editor to reduce its size. Use https://jakearchibald.github.io/svgomg/

  3. Create IconsetCreator Java class as a copy of EditingIconsetCreator for example. Modify srcFolder = "src/main/resources/images_editing"; iconsetId = "ttgams-editing"; final String outputFile = "src/main/resources/ttgams-editing.html";

    Run it and as result it will generate ttgams-.html

    Example: EditingIconsetCreator was created and ttgams-editing.html generated.

  4. Include name of this generated html file into ttgams-web-ui\src\main\web\fielden\desktop-application-startup-resources.html

    <link rel="import" href="/resources/ttgams-editing.html">

  5. Use your icon where required in the code as following:

    StandardActions.SEQUENTIAL_EDIT_ACTION.mkActionWithIcon(WorkActivityTaskTextualResultSheet.class, "ttgams-editing:sequential-edit", Optional.empty()) "ttgams-editing:sequential-edit" consists of "name_of_the_html_file:id"


Troubleshooting

  1. When running a run configuration, Eclipse responds with an error message to the effect that the main class cannot be found, despite all evidence to the contrary.

    1. Delete and re-create the run configuration.
  2. Running unit tests from within Eclipse hangs.

    1. There could be a corrupt H2 database left over from previous tests. Locate the delete such databases, often found in the *-dao/src/test/resources/db directory.

TODO

  • [On behalf of ML who is doing a poor job of updating the wiki page himself] Once this issue (#73 in tg) has been completed, could you please update the TG consultancy guidelines wiki page to provide clear use cases, patterns and guidelines on where and how to apply annotations @Required, @NotNull and @Final
  • Date-only: In setters use something like this.date = DateTimeUtil.dateOnly(date); e.g. Timesheet.setDate(). Probably expand this into a section on the use of dates.
Clone this wiki locally