This website is in transition. Most of the existing contents have been rearranged. You will find a big part of the older staff marked as retired. All references to the retired projects are moved to the Archive pages. Meanwhile, new projects are under development. The main purpose of the existing goal remains the same. Create a model of what you want to have in some sort of universal modeling language, and generate what is needed to run as a real application.
Creating ExtractBooks utility in Swift 4
Introduction
The Booxter for Mac application allows you to collect information about books in your home library. This post describes the creation of a simple ExtractBooks utility that is extracting some data about books stored in the SQLite database used by the Booxter and loads this data into the MongoDB database. The purpose of this little project is to build expertise in creating a server-side logic using Swift programming language, and this is the first step. It should help later in creating generation templates used by the Data Model Widgets to generate source code in Swift for the different databases like MYSQL, SQLite or PostgreSQL.
Exporting data from Booxter
Booxter helps you track your collections of books, music, movies, and comics on your Mac. You will find more about Booxter here. You can download a trial version and play with it if you like.
Here is a snapshot of how the Booxter main screen looks.
The Booxter creates a library at the location of your choice.
You can use Show Package Contents to explore inner structure of the library.
You will find that data.sql is local SQLite database keeping data about books, music, and videos. You will notice that images subdirectory contains some image files. Those are small snapshots of the book’s covers.
You can open the data.sql file using DB Browser for SQLite or similar product. Here is a list showing the structure of the database used by the application.
We are going to use only a fraction of the data describing books. Here is a sample of the ZBXITEM table keeping most of the details about each book.
Two other tables: ZBXAUTHOR and ZBXIMAGE have additional details we are going to export from the database. The ZBXIMAGE table does not keep images itself, but rather a name of the file stored in the images subdirectory.
It makes sense to mention that path to the data.sql file looks something like:
file:///Users/User/Desktop/BooxterTest.bxbooklib/data.sql
Creating development environment
The first steps will create a development environment for the project. It will allow assembling of all products required to develop and build utility successfully. Installing SQLite and MongoDB is out of the scope of this post.
Step 1. Create a folder for the project
$ mkdir ExtractBooks $ cd ExtractBooks $ swift package init --type executable
Step 2. Create package file inside a new directory
You need to create Package.swift file in the newly created directory.
import PackageDescription let package = Package( name: "ExtractBooks", dependencies: [ .package(url: "https://github.com/IBM-Swift/Swift-Kuery-SQLite.git", from: "1.1.0"), .package(url: "https://github.com/OpenKitten/MongoKitten.git", from: "4.0.0") ], targets: [ .target( name: "ExtractBooks", dependencies: ["MongoKitten", "SwiftKuerySQLite"]), ] )
You can notice that our project depends on two other packages: Swift-Kuery-SQLite and MongoKitten. We can create Xcode project now. Rest of the development will be done using Xcode development tool for Mac running macOS.
$ swift package generate-xcodeproj
Structure of the utility
This utility is activated from the command line. It takes some parameters describing the location of the SQLite and MongoDB databases. Here are sample parameters.
-u "file:///Users/User/Desktop/BooxterTest.bxbooklib/data.sql" "mongodb://localhost/library" "books"
Here is the content of the main.swift file’.
// // main.swift // // Created by Marek Stankiewicz on 16/06/2018. // import Foundation let mainLogic = MainLogic() if CommandLine.argc < 1 { mainLogic.consoleDialog.printUsage() } else { let returnStatus = mainLogic.startProcessing() if returnStatus.rc == 0 { mainLogic.consoleDialog.writeMessage(returnStatus.message, to: OutputType.standard) } else { mainLogic.consoleDialog.writeMessage(returnStatus.message, to: OutputType.error) } exit(Int32(returnStatus.rc)) }
Here is the MainLogic.swift file with the functionality responsible for processing the input parameters.
// // MainLogic.swift // // Created by Marek Stankiewicz on 16/06/2018. // import Foundation import CoreData enum OptionType: String { case upload = "-u" case help = "-h" case unknown init(value: String) { switch value { case "-u": self = .upload case "-h": self = .help default: self = .unknown } } } enum ProcessResponse { case successfull case noSQLiteConnect(String) case noMongoDBConnect(String) case problemCreatingMongoDBDoument(String) case problemProcessingMongoDB(String) case problemOpeningImageFile(String) } class MainLogic { let consoleDialog = ConsoleDialog() func startProcessing() -> (rc: Int, message: String) { let arguments = CommandLine.argc if arguments < 1 { return (rc: ReturnCode.command_line_usage_error.rawValue, message: "No option parameters provided.") } let option = getOption(CommandLine.arguments[1]) switch option { case .upload: let params = unpackCmdParams(array : CommandLine.arguments) if params == nil { return (rc: ReturnCode.command_line_usage_error.rawValue, message: "Wrong number of parameters for -u function.") } if !validateURL(url: (params?.sqLite)!) { return (rc: ReturnCode.command_line_usage_error.rawValue, message: "URL to SQLite database malformed.") } if !validateURL(url: (params?.mongodburl)!) { return (rc: ReturnCode.command_line_usage_error.rawValue, message: "URL to MongoDB database malformed.") } let uploadAction = UploadAction(filename: (params?.sqLite)!) let processResponse = uploadAction.upload(fromSQLite: (params?.sqLite)!, toMongoDB: (params?.mongodburl)!, collectionName: (params?.collectionName)!) return createRetrunMessage(processResponse: processResponse) case .help: consoleDialog.printUsage() case .unknown: return (rc: ReturnCode.command_line_usage_error.rawValue, message: "Unknown option.") } consoleDialog.printUsage() return (rc: ReturnCode.command_line_usage_error.rawValue, message: "Unknown option.") } func validateURL(url : String) -> Bool { let nsurl = NSURL(string: url) return nsurl == nil ? false : true } func unpackCmdParams(array : [String]) -> (sqLite : String, mongodburl : String, collectionName : String)? { if array.count != 5 { return nil } return (array[2], array[3], array[4]) } func getOption(_ option: String) -> OptionType { return OptionType(value: option) } func createRetrunMessage(processResponse: ProcessResponse) -> (rc: Int, message: String) { switch processResponse { case .successfull: return (0,"Processing completed successfully") default: return (1, self.getDescription(processResponse: processResponse)) } } func getDescription(processResponse: ProcessResponse) -> String { switch processResponse { case .successfull: return "successful" case let .noSQLiteConnect(message): return message case let .noMongoDBConnect(message): return message case let .problemOpeningImageFile(message): return message case let .problemCreatingMongoDBDoument(message): return message case let .problemProcessingMongoDB(message): return message } } }
The process of extracting from SQLite and loading to the MongoDB is in the UploadAction.swift file.
// // UploadAction.swift // BooksUpload // // Created by Marek Stankiewicz on 25/06/2018. // import Foundation import SwiftKuery import SwiftKuerySQLite import MongoKitten class ZBXITEM: Table { let tableName = "ZBXITEM" let zpkitem = Column("Z_PK", Int32.self) let zisbn = Column("ZISBN") let ztitle = Column("ZTITLE") let zlocation = Column("ZLOCATION") let zproductdescription = Column("ZPRODUCTDESCRIPTION") let zpublisher = Column("ZPUBLISHER") let zpages = Column("ZPAGES", Int32.self) let zlanguage = Column("ZLANGUAGE") let zdatepublished = Column("ZDATEPUBLISHED", Timestamp.self, notNull: false) } class ZBXIMAGE: Table { let tableName = "ZBXIMAGE" let zpkimage = Column("Z_PK") let zfilename = Column("ZFILENAME") } class ZBXAUTHOR: Table { let tableName = "ZBXAUTHOR" let zpkauthor = Column("Z_PK", Int32.self) let zname = Column("ZNAME") } class UploadAction { var processResponse : ProcessResponse = ProcessResponse.successfull var connection : SQLiteConnection init(filename: String){ self.processResponse = ProcessResponse.successfull self.connection = SQLiteConnection(filename: filename) } func upload(fromSQLite : String, toMongoDB: String, collectionName: String) -> ProcessResponse { connection.connect() { error in if error == nil { let url = URL(string: fromSQLite) let imagedir = url?.deletingLastPathComponent().absoluteString process(toMongoDB: toMongoDB,collectionName: collectionName, imagedir: imagedir! + "images/") } else if let error = error { processResponse = ProcessResponse.noSQLiteConnect("Error opening database: \(error.description)") } } return processResponse } func extractTimestamp(number: Any) -> String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy-MM-dd'.'HH.mm.ss.00000" formatter.timeZone = TimeZone(secondsFromGMT: 0) if number is Int32 { return formatter.string(from: Date(timeIntervalSince1970: Double(number as! Int32))) } else if number is Double { return formatter.string(from: Date(timeIntervalSince1970: number as! Double)) } return "" } func process(toMongoDB: String, collectionName: String, imagedir: String) { do { let library = try MongoKitten.Database(toMongoDB) let books = library[collectionName] let zbxitems = ZBXITEM() let query = Select(zbxitems.zpkitem, zbxitems.zisbn, zbxitems.ztitle, zbxitems.zlocation, zbxitems.zproductdescription, zbxitems.zpublisher, zbxitems.zpages, zbxitems.zlanguage, zbxitems.zdatepublished, from: zbxitems) connection.execute(query: query) { queryResult in if let rows = queryResult.asRows { for row in rows { var book: Document = Document() // Primary key let zpkitem = row["Z_PK"].unsafelyUnwrapped as! Int32 book.append(zpkitem, forKey: "_id") // ISBN Number let zisbn = row["ZISBN"] if zisbn.unsafelyUnwrapped != nil { book.append(zisbn! as! Primitive, forKey: "isbn") } // Title let ztitle = row["ZTITLE"] if ztitle.unsafelyUnwrapped != nil { book.append(ztitle! as! Primitive, forKey: "title") } // Location let zlocation = row["ZLOCATION"] if zlocation.unsafelyUnwrapped != nil { // book.append(zlocation! as! Primitive, forKey: "location") } // Product Description let zproductdescription = row["ZPRODUCTDESCRIPTION"] if zproductdescription.unsafelyUnwrapped != nil { book.append(zproductdescription! as! Primitive, forKey: "description") } // Publisher let zpublisher = row["ZPUBLISHER"] if zpublisher.unsafelyUnwrapped != nil { book.append(zpublisher! as! Primitive, forKey: "publisher") } // Pages let zpages = row["ZPAGES"] if zpages.unsafelyUnwrapped != nil { book.append(zpages as! Primitive, forKey: "pages") } // Language let zlanguage = row["ZLANGUAGE"] if zlanguage.unsafelyUnwrapped != nil { book.append(zlanguage! as! Primitive, forKey: "language") } // Date Published let zdatepublished = row["ZDATEPUBLISHED"] if zdatepublished.unsafelyUnwrapped != nil { book.append(self.extractTimestamp(number: zdatepublished.unsafelyUnwrapped ?? "unknown"), forKey: "datepublished") } var authors = [String]() let zbxauthor = ZBXAUTHOR() let queryAuthor = Select(zbxauthor.zpkauthor, zbxauthor.zname, from: zbxauthor).where(zbxauthor.zpkauthor == Parameter()) self.connection.execute(query: queryAuthor, parameters: [Int(zpkitem)]) { queryResult in if let rows = queryResult.asRows { for row in rows { // Author Name let zname = row["ZNAME"] if zname.unsafelyUnwrapped != nil { authors.append(zname as! String) } } } } book.append(authors, forKey: "authors") let zbximage = ZBXIMAGE() let queryImage = Select(zbximage.zpkimage, zbximage.zfilename, from: zbximage).where(zbximage.zpkimage == Parameter()) self.connection.execute(query: queryImage, parameters: [Int(zpkitem)]) { queryResult in if let rows = queryResult.asRows { for row in rows { // Author Name let zfilename = row["ZFILENAME"] if zfilename.unsafelyUnwrapped != nil { let path = imagedir + (zfilename.unsafelyUnwrapped as! String) // File Name let url = URL(string: path) do { let file: FileHandle? = try FileHandle(forReadingFrom: url!) if file != nil { let data = file?.readDataToEndOfFile() file?.closeFile() book.append(Binary(data: data!, withSubtype: Binary.Subtype.generic), forKey: "image") } } catch { self.processResponse = ProcessResponse.problemOpeningImageFile("Problem opening image file: \(String(describing: url))") } } } } } do { try books.append(book) } catch { self.processResponse = ProcessResponse.problemCreatingMongoDBDoument("Problem appending a new document for item: \(Int(zpkitem))") } } } } } catch { self.processResponse = ProcessResponse.problemProcessingMongoDB("Problem processing MongoDB") } return } }
The entire project is in the GitHub here.
Sample data from the database created using the MongoDB follows:
Summary
This is a just the first example of using Swift. More will follow. Next step will be a server logic written using Kitura framework.
Project: User-Added Function Implemented by Action Block (Part2)
I mentioned about launching a new incubator project. The primary goal was to build a tool helping in the development of so-called user-added functions for the CA Gen. It was couple weeks ago. The first experimental version of the Eclipse project delivering such tool you can find here published on the GitHub for the public viewing. You will be able to download the standalone UAF utility from here. I would like to remain you that all software downloaded from this website is experimental and you should not use it in the production environment until tested adequately.
Table of Contents
This post provides a detailed information how to install and use the UAF utility by creating a simple user-added function in the CA Gen Model, later referred to as a function.
Installing software
Step 1. Download and expand zip file
The utility consists of the executable jar file and directory with the number of jar files required to run the UAF utility. Your installation directory will look as follows.
Step 2. Copying CA Gen jar files
The utility is using a number CA Gen jar files which you can find in the CA Gen installation directory. You need to copy them into the subdirectoryuad_lib
. They are:
com.ca.gen.jmmi_8.5.0.00333.jar, abrt.jar, csu.jar, odc.jar, vwrt.jar
Above jar files containing JMMI API allowing access a CA Gen model located on the workstation and some runtime classes allowing run mockup software required to do unit-testing of your newly developed functions.
Step 3. Starting utility
You need to run the utility from the command line. Here is how to display help information.
Utility User-Added Function (UAF), Version 0.5 Usage: java -jar uaf.jar modelpath cagenpath [ -forceall | -testonly | -sourcesonly] where: -forceall force clean up entire generation frastructure -testonly generate test classes only -sourcesonly regenerate implementation classes only
You will learn more about parameters and options later.
Defining user-added function
We are going to use the CA Gen Toolset to develop a library of functions. This is very easy and straightforward process.
Step 1. Create a new CA Gen model
Best practice is to start creating a new dedicated empty model and use it to define all functions in your library. Additionally, you can use such model later to implement some of your functions using some purpose written action blocks created in this model.
Step 2. Set environment for generation
You will notice that your model has one Business System having a name like your model. Select Environment under Construction and select our business system by double-clicking on the name. You will see the following window pane opens allowing set some properties for the selected business system.
The utility can generate only for the Java environment at present. Therefore, be sure that selected operating system is JVM, language is Java and you set correct name for the package.
Step 3. Create action block
You are defining a function by creating your action block. Not all action block can serve as a template. Qualifying action block should have some attribute views defined in the import section and single attribute in the export section.
We are going to create a rather simple function first whose whole purpose will be to add two numbers and return a sum of them as a result. We expect that our new function will take two parameters of type number and return a single number.
Model : TSTUAF1 10 Sept. 2017 22:24 Subset: (complete model) BSD Action Block: FAB01 ___________________________________________________________________________ Action Block Description: #meta { import eu.jgen.notes.uaf.proc.Function; @Function (CGName="XFAB01", useselec=false, impl=true) // Adds two numbers and returns sum of those numbers. } +- FAB01 | IMPORTS: | Work View imp1 ief_supplied (optional,transient,import only) | count (optional) | Work View imp2 ief_supplied (mandatory,transient,import only) | count (mandatory) | EXPORTS: | Work View exp ief_supplied (transient,export only) | count | LOCALS: | ENTITY ACTIONS: | | USE ab02 | WHICH IMPORTS: Work View imp1 ief_supplied TO Work View imp1 ief_supplied | Work View imp2 ief_supplied TO Work View imp2 ief_supplied | WHICH EXPORTS: Work View exp ief_supplied FROM Work View exp ief_supplied +--
The above action block qualifies as a template for the function and we will use it to create such function. Also, please notice that action block has one statement. The statement is USE
statement and invokes another action block. This is an unnecessary complication, but it demonstrates that function’s implementation can invoke a number other action blocks if necessary.
Model : TSTUAF1 10 Sept. 2017 22:28 Subset: (complete model) BSD Action Block: AB03 ___________________________________________________________________________ Action Block Description: +- AB03 | IMPORTS: | Work View imp1 ief_supplied (optional,transient,import only) | count (optional) | Work View imp2 ief_supplied (mandatory,transient,import only) | count (mandatory) | EXPORTS: | Work View exp ief_supplied (transient,export only) | count | LOCALS: | Work View loc ief_supplied | command | ENTITY ACTIONS: | | SET exp ief_supplied count TO imp1 ief_supplied count + imp2 ief_supplied count +--
The statement in the last invoked action block has our implementation logic and simply adds two numbers. On this occasion, we are going to use newly created action block to implement the logic of our function as well. Please notice that only first action block has the annotation.
Step 4. Annotate action block
We are using Java style annotations to mark action block as a template creating function. You find the following annotation added to the description of our action block.
The @Funtion
annotation allows providing additional information required for creating a function in the CA Gen model. The CGName
parameter specifies the generated function internal name. It should be unique among already existing functions including those provided by the CA Gen. The useselec
parameter indicates whether the function will take part in a database SELECT statement as selection criteria. The parameterimpl
indicates whether the action block should be used as implementation. You may decide to implement function entirely outside of the CA Gen or only partially using also dedicated action blocks.
This step completes a process of defining the function. Rest of processing is generating artifacts using our UAF utility.
Step 5. Running utility
You will need Java 1.8 to run, but you can regenerate project with another version of Java. Version 1.7 should be fine. Utility takes two parameters. The first parameter points to the location of the CA Gen model with templates created in the previous step. The second parameter points to the location of the CA Gen itself. Here is sample output produced when running the utility.
Utility User-Added Function (UAF), Version 0.5 Starting... Connected to local model: TSTUAF1 WARNING: All subdirectories were forced to be emptied. INFO: Adding subdirectory. INFO: Adding java functions subdirectory. INFO: Adding meta-inf subdirectory. INFO: Generation directory used: C:\jgen.notes.models.test\tstuaf1.ief\notes INFO: Project file with Java nature created. INFO: Classpath file with CA Gen libraries created. INFO: Found 1 function(s). INFO: XML file for 1 function(s) created. INFO: Creating package sub-directories for functions: C:\jgen.notes.models.test\tstuaf1.ief\notes\src\com\ca\gen\abrt\functions INFO: Function com.ca.gen.abrt.functions.XFAB01 generated. INFO: Creating package sub-directories for functions: C:\jgen.notes.models.test\tstuaf1.ief\notes\src\com\ca\gen\abrt\functions\test INFO: Function Test Classcom.ca.gen.abrt.functions.XFAB01Tests generated. WARNING: Test class was regenerated. All previous modifications were lost. INFO: Implementation action block AB03 generated. INFO: Implementation action block AB02 generated. INFO: Implementation action block FAB01 generated. INFO: MANIFEST.MF file created. Processing completed.
Utility produces some messages as progress thru the generation process. The utility does not modify your model and is accessing model in the read-only mode.
Step 5. Reviewing generated artifacts
You will find that your local model directory’s structure has changed and you have a new subdirectory notes
and its internal structure is as follows.
The utility creates Eclipse plug-in project structure that you can import into the Eclipse as a new project. The project has Java nature, and all Java artifacts will be compiled as soon import completes.
Step 6. Import project into Eclipse
You need to start Import wizard, select Existing Projects into Workspace and press Next.
You need select root directory and our new project will show up on the list of projects. Select project and press Finish.
You will notice that you have a new project in your workspace. The project is instantly built. You can expand folders to see what has been generated for you.
Starting from the top, the folderjava
has sources of all action blocks marked by annotation and all other action blocks that may be used by the annotated action block. Next, the foldersrc
contains the source of the function and testing harness for the unit testing. On the bottom, you will find XML userfunctions.xml
file with the definition of the function. The CA Gen Toolset will use this file to create the function inside the CA Gen model.
Testing user-added function
Your functions can be relatively straightforward and tested easily, but in some cases, you will need to ensure a proper test cycle. This includes unit test done in a separation of other software components. The UAF utility can help generating for you some testing classes invoking the function with your choice of input parameters and checking if result matches expected value. The proposed testing framework provides mock-up setting simulating real runtime environment. You do not need to run the web server and deploy test application to the server to conduct such tests.
Here is generated code for our sample function.
/** * Code generated by UserAddedFunctionProcessor */ package com.ca.gen.abrt.functions.test; import static org.junit.Assert.assertTrue; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import com.ca.gen.abrt.functions.XFAB01; import com.ca.gen.vwrt.types.IntAttr; import com.google.inject.Guice; import com.google.inject.Injector; import eu.jgen.notes.ab.direct.mgr.ActionBlockModule; import eu.jgen.notes.ab.direct.mgr.DBMSType; import eu.jgen.notes.ab.direct.mgr.DirectException; import eu.jgen.notes.ab.direct.mgr.SessionContext; import eu.jgen.notes.ab.direct.mgr.SessionManager; import eu.jgen.notes.ab.direct.mgr.TransactionManagerType; /** * @author * */ public class XFAB01Tests { static Injector injector; private static SessionManager sessionManager; /** * @throws java.lang.Exception */ @BeforeClass public static void setUpBeforeClass() throws Exception { try { injector = Guice.createInjector(new ActionBlockModule()); sessionManager = injector.getInstance(SessionManager.class); sessionManager.initialize(TransactionManagerType.None, DBMSType.None).begin(); } catch (DirectException e) { e.printStackTrace(); } } /** * @throws java.lang.Exception */ @AfterClass public static void tearDownAfterClass() throws Exception { } @Test public void sampleTest() { IntAttr result = IntAttr.getInstance(); XFAB01.XFAB01(new Object(), new SessionContext(sessionManager.getGlobData()), sessionManager.getGlobData(), 2, 2, result); assertTrue(result.get() == 4); } }
The only change to the generated code was to modify method assertTrue
and set values for two input parameters.
The test classes are not always generated. You need to run the utility with the-testonly
parameter to have them generated.
Delivering user-added functions
The CA Gen does not know about your new function at this point. You need CA Gen Toolset to find user-added function and create relevant changes in the Gen model. One of the generated artifacts is the XML file.userfunctions.xml
You will find file having the same name in one of the directories created after CA Gen was installed. The directory name isC:\ProgramData\CA\Toolset
. This file contains some user-added functions provided by the CA Gen. You need to merge those files or, for time being, rename original file and copy in your one generated by our UAF utility. Once you start the CA Gen Toolset our function will be visible and available for use.
You will find more about user-added function here on the CA Gen Documentation website.
Developing application using functions
Here a snapshot of the procedure step developed in the another model that uses the newly developed function.
Model : USEUAF1 10 Sept. 2017 23:10 Subset: (complete model) Procedure Step: USRUAF1 ___________________________________________________________________________ Procedure Step Description: Action Block Description: +- USRUAF1 | IMPORTS: | Work View imp1 ief_supplied (optional,transient,import only) | count (optional) | Work View imp2 ief_supplied (optional,transient,import only) | count (optional) | EXPORTS: | Work View result ief_supplied (transient,export only) | count | Work View exp1 ief_supplied (transient,export only) | count | Work View exp2 ief_supplied (transient,export only) | count | LOCALS: | ENTITY ACTIONS: | +-- +- EVENT ACTION usruaf1_pb_add_click | SET result ief_supplied count TO fab01(imp1 ief_supplied count, imp2 ief_supplied count) +--
Deploying application
There is no difference to the standard way of building and deploying an application with user-added functions. However, you need to remember to build a jar file with some classes from the model you are using to develop your functions.
You need to make your jar visible to the Build Tool so application consuming your function can successfully compile your application. You jar file needs also to be included in the application ear file by the Assembly process.
You can see the sample application running after deploying to the Weblogic server.
Java Wrapping for CA Gen Encyclopedia API
Historically first officially published CA Gen API was the Encyclopedia and Workstation Application Program Interface (API) software for CA Gen. It supports the Host Encyclopedia and the Client Server Encyclopedia (CSE) for all available API platforms and databases. It also supports local access to workstation models, Host Encyclopedia, and the CSE for all available API platforms and databases.
The Encyclopedia API is a set of C functions, include files, and libraries. You must be familiar with the C programming language and the software development tools for the platform on which you are working in order to use the Encyclopedia API. You must also be familiar with the CA Gen meta-model.
Today we have CA Gen JMMI API written in Java. The JMMI was introduced when CA Technologies announced the CA Gen Studio built on the Eclipse platform. The JMMI is frontend written in Java, but most of their methods are native methods written in C++. The JMMI does not offer access to the models stored in one of the supported encyclopedias (HE and CSE). Therefore, the Encyclopedia API is the only way to access models stored in the HE and CSE.
The JGen Notes Core Java project is offering Java wrapping for the models located on the workstation only (limitations of the CA Gen Free Gen), but those interfaces can be easily extending to the model located in the remote places like Client Server Encyclopedia (CSE) or Host Encylopedia (HE).
You can find sources of the implementation here and here.
Developing plug-ins in Java for Gen Toolset
Overview
CA Gen allows applications to access the currently opened model through a mechanism that is known as a plug-in. The application should register itself in the Windows registry, and from there the CA Gen toolset knows how to call it. Toolset adds a menu item to the CA Gen “Plug-in” menu. The developer starts plugin from the toolset after Toolset opens model for processing.
You will find more information about CA Gen Toolset Automation here.
However, there is a problem when you are willing to develop something in Java language. The problem is because the plug-in application should instantiate a pointer to the currently opened toolset using the toolset OLE automation interface. Such interface allows the application to access the functionality provided by the interface. Unfortunately, Java program cannot do it without using some software bridging those two different technologies.
Fortunately, Eclipse is using the SWT GUI framework. SWT does allow to integrated Microsoft application via OLE (Object Linking and Embedding). You can locate jar file org.eclipse.swt.win32.win32.x86_3.3.0.v3346.jar
in the subdirectory of your Eclipse installation. Subdirectory name is plugin.
It has the SWT Win32 OLE implementation classes. Classes provide the public API to the Microsoft Win32 Object Linking and Embedding mechanism that the win32 variant of SWT is capable of using. Referencing any of the classes in this package directly guarantees that the code is platform specific.
You can consult tutorial “Microsoft and Java Integration with Eclipse” written by Lars Vogel here. It helps understand the SWT Win32 OLE implementation classes and shows examples of the use.
Idea developing plugs using Java resulted in the need for some easier ways to access the Gen model and hide the complexity of the SWT Win32 OLE implementation classes. The Toolset Automation Wrapper is a package of Java classes doing just that. The Toolset Automation Wrapper does not cover all functionality offered by the Toolset Automation.
You can download Java documentation and jar file with the wrapper classes here.
You can find Eclipse project on GitHub here.
Sample Application
Sample application connects to the currently opened model in the CA Gen Toolset and lists all business systems defined in the model. The application is a simple Java class with the method main().
You need to run the application from the command line. Yon need be sure that you have two jar files on the classpath. They are jmmi
and ToolsetAutomationWrapper
.
Here is the source of the application:
package eu.jgen.notes.automation.example; import com.ca.gen.jmmi.schema.ObjTypeCode; import com.ca.gen.jmmi.schema.ObjTypeHelper; import com.ca.gen.jmmi.schema.PrpTypeCode; import com.ca.gen.jmmi.schema.PrpTypeHelper; import eu.jgen.notes.automation.wrapper.JGenEncyclopedia; import eu.jgen.notes.automation.wrapper.JGenFactory; import eu.jgen.notes.automation.wrapper.JGenModel; import eu.jgen.notes.automation.wrapper.JGenObject; public class ListAllBusinessSystems { public static void main(String[] args) { // create instance of the factory class JGenFactory factory = JGenFactory.eINSTANCE; // create instance of the encyclopedia JGenEncyclopedia ency = factory.createEncyclopedia(); // connect to the encyclopedia (model needs to be opened in the toolset // already) ency.connect(); // select model for processing JGenModel genModel = ency.findModels()[0]; System.out.println(genModel.getName()); // find all business system in the model JGenObject[] bussyss = genModel.findTypeObjects(ObjTypeHelper.getCode(ObjTypeCode.BUSSYS)); for (JGenObject bussys : bussyss) { System.out.println(bussys.findTextProperty(PrpTypeHelper.getCode(PrpTypeCode.NAME))); } } }
The application will print the following messages on the console for the sample.ief model which is coming with the standard CA Gen installation on the workstation.
GEN SAMPLE MODEL 8 6 CORPORATE_MANAGEMENT GUI_CORPORATE_MANAGEMENT COOP_CORPORATE_MANAGEMENT
Summary
Solution and sample application have been developed using the following software:
- Windows 10
- CA Gen 8.6 (Free)
- Java Version 8 (Update 20)
- Eclipse Neon.3 Release (4.6.3)
Researching contents of local model using JGen Notes Walkency
CA Gen Documentation provides Encyclopedia API Reference which is place providing a lot of information about internal of the CA Gen model and metamodel which describes a structure of the model. Metamodel shows the properties and object associations that are inherited and from which objects they are inherited. The metamodel is the CA Gen Information Model. Additionally, you will find in your CA Gen installation directory file with published Object Decomposition Report presented in a hierarchical format. Name of the file is
odrpta.
This is the Microsoft’s standard help format (chm).
You will find here a link to the web page describing utility allowing explore the internal structure of the local model on the workstation easily and comprehensively. Its name Walkency refers to traditional tools used to display contents of the model stored in the Host Encyclopedia (HE) and Client Server Encyclopedia (CSE). JGen Notes Walkency is the reincarnation of such tool dedicated for the developers using the CA Gen Toolset. JGen Notes Walkency is written entirely in Java and developed as Eclipse Rich Client.
Technically, from the CA Gen Toolset perspective, JGen Notes Walkency is a plug-in registered with the CA Gen Toolset and available for use when selected from the menu system of the CA Gen Toolset.
Developing utility generating applications from the CA Gen model
Overview
This blog entry focuses on explaining some hidden possibilities to activate the generation process using the CA Gen JMMI API. Selecting generation dialog from the Gen Toolset or Gen Studio is commonly known as the only way to trigger generation process, but CA Gen JMMI API allows controlling generation process outside the Toolset by developing your own utilities that can run online or in batch mode depending on your needs.
It makes sense to mention that historically the CA Gen Toolset was designed allowing the full cycle of the development, starting from the designing model, generating code in a number of the programming languages, allows building applications for some hardware and software platforms. This also includes dispatching packaged applications to the various operating systems and build them using native tools available on the target platform.
An integral part of the development cycle includes generating source code that can later be delivered and built in many different various target environments. Also, the CA Gen Toolset running on the Windows platform is not the only place where source code can be generated from the Gen Model. Please consult the CA Gen documentation to discover many possibilities that this software offers.
Standard generation process starts by selecting toolset dialog offering selection of the generation choices. This offers a selection of the operating system, programming language, transaction processing environment and database to mention few options. The whole process is under control of the toolset and strictly regulated and it has to be triggered manually from the workstation.
The generation process is implemented internally by a number of utilities that are triggered by the CA Gen Toolset transparently to the developer. Usually, the Gen Build Tools is activated later to start building application or performs packaging of the deliverables so packages of the generated source code can be transferred to one of many target platforms to complete building process. Those utilities are considered by the CA as internal and they are not documented so should not be used by the inexperienced developer.
The CA Gen JMMI API is quite elaborate and complex solution. We are going to focus only on the specific fragment of the API that deals with the generation process.
We are going to walk thru the sample utility built using JMMI API. Our simple utility will open selected local model and will generate code in Java and use JDBC as an access method to the database for the selected action block. Utility stores generated code at the location provided by the user.
The utility will be developed in Java and run from the command line taking a number of parameters. We are using Eclipse project for the development and project you can download from the Github here.
JMMI API
The CA Gen JMMI API is a hidden treasure. You will not find any part of the official documentation manual explaining how JMMI API works and how it can be used. There is a dedicated manual called Encyclopedia API Reference, but the JMMI API is not mentioned there. It is strange because the whole CA Gen Studio which is an Eclipse-based Java application is using JMMI API as the main way to access local model kept on the workstation. Fortunately, you will find basic Java type of documentation in one of the CA Gen Toolset installation directories. There are not too many rich explanatory comments, but nicely crafted name for classes and methods allows to be familiar with the API very quickly.
Here is where you find your JMMI API.
The CA Gen JMMI API is a really clever number of classes and methods are mostly native and implemented in C++ that can be executed only on the workstation running Window operating system, so you cannot port an application using JMMI API to any other platform. The CA Gen JMMI API can only access local models, but it looks like is designed to provide remote access to CA Gen encyclopaedias (Client Server Encyclopedia and Host Encyclopedia)in the future as well.
The JMMI provides a lot of classes, but we focus on only few of them. They are as follows:
- com.ca.gen.jmmi.Generation
- com.ca.gen.jmmi.GenerationData
- com.ca.gen.jmmi.GenerationData
Soample utility explained
Our utility is very simple. This is a Java class run from the command line and accepting a number arguments. In its simplest form you can start application as follows:
java ABGenerate -j "C:\jgen.notes.models\sample.ief" "c:\temp" "FILE_FUNCTIONS_READ"
You specify location of your local model, location for generated source code and name of the action block to be generated.
The below snapshot of the code shows processing of the input arguments and what part of the application is responsible for the executing requests.
public static void main(String[] args) { ABGenerate utility = new ABGenerate(); try { System.out.println("Action Block Generaion Utility, Version 1.0"); if(args.length == 0) { displayHelp(); } if(args.length == 1 && args[0].equals("-h")) { displayHelp(); return; } if(args.length == 4 && args[0].equals("-j")) { utility.start(args[1], args[2], args[3]); System.out.println("Completed."); return; } if(args.length == 2 && args[0].equals("-o")) { utility.options(args[1]); System.out.println("Completed."); return; } displayHelp(); } catch (EncyException e) { e.printStackTrace(); } catch (ModelNotFoundException e) { e.printStackTrace(); } } private static void displayHelp() { System.out.println("Usage: java ABGenerate [ ]"); System.out.println("\toptions:"); System.out.println("\t\t-h help"); System.out.println("\t\t-j to generate for Java and "); System.out.println("\t\t-o to display generation choices for "); }
The main function of the application starts here and will be explain step-by-step.
private void start(String modelpath, String sourcepath, String abname) throws EncyException, ModelNotFoundException { openModel(modelpath); System.out.println("Generating action block " + abname + " into " + sourcepath + "\\" + JAVA_SUBDIRECTORY); generate(findActionBlock(abname), sourcepath, abname); closeModel(); } private void openModel(String modelpath) throws EncyException, ModelNotFoundException { ency = EncyManager.connectLocal(modelpath); model = ModelManager.open(ency, ency.getModelIds().get(0)); System.out.println("Openning " + model.getName()); } private void generate(long id, String sourcepath, String abname) { generation = GenerationManager.getInstance(model); generateActionBlock(id, sourcepath); } private void generateActionBlock(long id, String sourcepath) { generation.generateActionBlock(generation.getModelContext(), id, sourcepath, JAVA_SUBDIRECTORY, "ABC", "JVM", "JDBC", "INTERNET", "JAVA", "NO", "JAVAC", "YES"); } private long findActionBlock(String abname) { ObjId objId = model.getObjIdByName(ObjTypeCode.ACBLKBSD, PrpTypeCode.NAME, abname); return objId.getValue(); } private void closeModel() { model.close(); ency.disconnect(); }
- The first task is to open model. Class
EncyManager
has a static methodconnectLocal()
allowing to connect to the encyclopedia. It is a little bit misleading because we are really connecting to the single local model which is not kept in the encyclopedia. - Class
ModelManager
has the static methodopen()
allowing to open selected model. An instance of the classModel
is now available and represent a real model. Again, there is only one model at the chosen connection so we are going to pick the first one. - We can start generation process once the model is selected and opened. We need to obtain an instance of the generator first.
- Class
Generation
has dedicated methods to perform a specific type of generation. We are going to usegenerateActionBlock()
.
These methods performing generation takes a number of parameters representing generation choices. You can display possible choices for the type of generation using this utility by starting it as follows:
java ABGenerate -j "C:\jgen.notes.models\sample.ief"
Here is the code printing possible generation choices.
private void displayGenerationOptions() { generation = GenerationManager.getInstance(model); operatingSystemsChoices(); languagesChoices(); databaseChoices(); tpmonChoices(); compilerChoices(); communicationChoices(); } private void operatingSystemsChoices() { System.out.println("Operating System Choices:"); for (GenerationData generationData : generation.getOperatingSystemsChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } } private void databaseChoices() { System.out.println("Database Choices:"); for (GenerationData generationData : generation.getDatabaseChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } } private void tpmonChoices() { System.out.println("Tpmon Choices:"); for (GenerationData generationData : generation.getTpmonChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } } private void compilerChoices() { System.out.println("Compiler Choices:"); for (GenerationData generationData : generation.getCompilerChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } } private void communicationChoices() { System.out.println("Communication Choices:"); for (GenerationData generationData : generation.getCommunicationChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } } private void languagesChoices() { System.out.println("Language Choices:"); for (GenerationData generationData : generation.getLanguageChoices()) { System.out.println("\t" +generationData.getGenerationDataKey() + "= " + generationData.getGenerationDataValue()); } }
Our simple utility produces a couple messages at the completion of the job.
Action Block Generaion Utility, Version 1.0 Openning GEN SAMPLE MODEL 8 6 Generating action block FILE_FUNCTIONS_READ into c:\temp\sources Completed.
You will find also a text file cgdebug.err
created and summarizing what has been generated.
abg.exe, c:\temp\sources\java, JVM, JDBC, INTERNET, JAVA, JAVAC, OFF, *, *, ABC, *, *, *, *, YES. ABG generating Encyid: 22020096 os JVM lang JAVA dbms JDBC compiler JAVAC tpmon INTERNET MBCS set code page ID: 1252. Starting Action Block Generator Generated 1136 lines of Java. Writing output file: c:\temp\sources\java\com/ca FILEFUNC.java Wrote 548 lines. Writing output file: c:\temp\sources\java\com/ca FILEFUNC_IA.java Wrote 211 lines. Writing output file: c:\temp\sources\java\com/ca FILEFUNC_OA.java Wrote 177 lines. Writing output file: c:\temp\sources\java\com/ca FILEFUNC_LA.java Wrote 114 lines.
Summary
The sample application has been developed using the following software:
- Windows 10
- CA Gen 8.6 (Free)
- Java Version 8 (Update 20)
- Eclipse Neon.3 Release (4.6.3)
You can find project files here.
Changing images in the CA Gen generated web application during runtime
Introduction
Users converting their existing CA Gen Windows GUI clients to the new Web Generation features as provided with the CA Gen are facing many unsupported or having partial support for many features that they are free to use in the GUI Windows environment. This is because of a lot of functionality is provided using third party solutions based on the OLE technology that is not supported by many browsers.
Building a simple web application allows displaying different images on the page depending on the current transaction context becomes a challenge and requires using external logic. The CA Gen provides two web page design features allowing provide external logic. They are HTMLText and HTMLControl.
This post provides tips how to implement external logic using JavaScript and external frameworks. A simple application will be used as an example explaining how step by step achieves the desired result.
Example application
Web example application is very simple.

There is a drop down list box with a list of names. Picture of the person is displayed whenever a new name is selected.
Design
Design requires the creation of a single window with the mode set to HTML. Windows has two controls. One is drop-down list box and picture control.

External logic will be provided by the HTML Text located in the page header.
HTMLTextHeader is not visible on the page and will be executed once by the browser when a page is loaded.
Action block implementing procedure step displaying window does not have any logic of its own.

It has one statement and it is just to prevent error messages. Developers will provide a number of statements in normal circumstances.
Package, Generate, Build and Deploy
The application is ready to be generated once packaging of the application is completed. Build Tool will compile all required code and Assembly function will produce ear file with all what is required to run after deployment to the target application server. URL address to invoke the application from the browser will be something like below:
http://youraddress:7001/dynimg/disimage.jsp
The sample application has been developed using the following software:
- Windows 10
- CA Gen 8.6 (Free)
- Java Version 8 (Update 20)
- JQuery 3.2.0
- Weblogic Server Version 12.2.1.2.0
External logic explained
Writing handwritten JavaScript scripts is the main way to deliver external logic to the web pages generated from the CA Gen model when existing version of the software does not offer a suitable solution. Additionally, we are going to use jQuery library. The purpose of jQuery is to make much easier to use JavaScript when developing web pages and the jQuery is easy to learn.
Let us look first on HTML page that has been generated from the CA Gen model. The file name of the page is DISPLAY_IMAGE.html.
Code for drop-down list box look as follows:
<SELECT CLASS='DFGUICMG DropDownList' NAME='DropDownList' ID='DropDownList' style="dir:ltr; position:absolute; left:56; top:18; width:316; " > <OPTION VALUE=' ' ></OPTION> <OPTION VALUE='Kowalski' >Kowalski</OPTION> <OPTION VALUE='Skipper' >Skipper</OPTION> <OPTION VALUE='Private' >Private</OPTION> <OPTION VALUE='Rico' >Rico</OPTION> </SELECT>
Code for picture control looks as follows:
<IMG NAME='Picture' ID='Picture' CLASS='DFGUILIT Picture' TABINDEX=0 style=" position:absolute; left:56; top:88; width:315; height:322; " SRC='images/any.jpg' BORDER=0 WIDTH=315 HEIGHT=322 ALT='ANY' TITLE='ANY' >
Picture control generated above has SRC parameter set once during generation and cannot be changed inside action block. This is why we need a JavaScript to do it for us.
Part of the header section of the HTML page you will see included the following code:
<!-- HTMLTextHeader --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script> <script> $(document).ready(function(){ $("#DropDownList").change(function(){ $("#Picture").attr("src", "images/" + $("#DropDownList").val() + ".JPG"); }); });
You will notice that CA Gen generator included contents of the HTML Text control into the header section of the generated HTML. First <script> statement refers to the website with the jQuery library. The second <script> statement defines a function that will execute each time another value from the drop-down list box is selected. It listens to the change event and executes statement changing SRC parameter in the picture control. This action causes refresh and a new picture from another file is displayed in the browser.
This solution pickups images from the images subdirectory which is uploaded during deployment stage. It is important to remember to place all required images before deployment in the correct subdirectory of the model.
You can learn more about jQuery and how it works here.
Possibile modifications
This blog provides very simple example just to explain the main mechanism used creating external logic. More complicated modifications can be introduced using a similar approach. It could be more sophisticated image controls including video or images are stored in the database rather that in the file system and extracted dynamically when required. This will require to develop and implement external logic in Java using the CA Gen External Action Blocks mechanism. This can be a topic for another blog.
Links
Here you will find sample CA Gen model with the example used in this blog. Need to generate code, build web client application, assembly and deploy into one of the supported web servers.
Free Version of CA Gen 8.6 Now Available
A free version of CA Gen 8.6 is available on the CA Gen EDGE community. There are certain limitations in using of the free version of the software. Here is how the CA Technologies comments decision allowing free download of the product:
The free version of Gen is being published to allow new users to try out Gen w/o incurring any cost, for use in training, for proof-of-concept opportunities and for long time users who are using earlier releases of Gen to check out the latest and greatest version of the product.
Note that the free version may only be used to create new models and will not work with existing models. Also note that the installation of the free version does not allow models to be exchanged with an encyclopedia.
This is a very interesting product to learn. There are many ways to extend functionality of the product and use it to generate powerful software applications. A huge part of the JGen Notes will be devoted to discuss various possibilities offered by the free version of the CA Gen.
Please visit CA Gen EDGE community to learn more. You will need to join community before you can benefit from the free version of the software.