1. Project
1.1. Branches
1.1.1. master
JDK 11 compatible, built with JDK 11, fully modular.
Maven/Gradle groupId is.codion.jdk11.
1.1.2. jdk8
Main development branch, JDK 8 compatible, built with JDK 8.
Requires a JDK with JavaFX, for example the Zulu JDK FX.
Maven/Gradle groupId is.codion.jdk8.
1.2. Building
The Codion framework is built with Gradle and includes the Gradle Wrapper, so assuming you have cloned the repository and worked your way into the project directory you can build the framework by running the following command.
gradlew build
Note
|
This may take a few minutes, depending on the machine. |
To install the Codion framework into your local Maven repository run the following command.
gradlew publishToMavenLocal
1.3. Running the demos
Note
|
The demos use an embedded in-memory database, so changes to data do not persist. |
1.3.1. Local database connection
You can start by running a client from one of the demo projects (empdept, chinook, petstore or world) with a local database connection.
gradlew :codion-demos-chinook:runClientLocal
1.3.2. Remote database connection
In order to run a client with a remote connection we must first start the remote server.
gradlew :codion-framework-server:runServer
To run a demo client with a remote connection use the following command.
gradlew :codion-demos-chinook:runClientRMI
You can run the Server Monitor application to see how the server is behaving, with the following command.
gradlew :codion-swing-framework-server-monitor:runServerMonitor
Note
|
The client handles server restarts gracefully, you can try shutting down the server via the Server Monitor, play around in the client until you get a 'Connection refused' exception. After you restart the server the client simply reconnects and behaves as if nothing happened. |
2. Architecture
The Codion framework is based on a three tiered architecture.
-
Database layer
-
Model layer
-
UI layer
2.1. Database layer
The EntityConnection class defines the database layer. See Manual:EntityConnection
2.2. Model layer
The EntityModel class defines the model layer. See Manual:EntityModel.
2.3. UI layer
The EntityPanel class defines the UI layer. See Manual:EntityPanel.
3. Client
3.1. Features
-
Lightweight single threaded client with a simple synchronous event model
-
Provides a practically mouse free user experience
-
Graceful handling of network outages and server restarts
-
Clear separation between model and UI
-
Easy to use load testing harness provided for application models
-
UI data bindings for most common components provided by the framework
-
Implementing data bindings for new components is made simple with building blocks provided by the framework.
-
The default UI layout is a simple and intuitive “waterfall” master-detail view
-
Extensive searching and filtering capabilities
-
Flexible keyboard-centric UI based on tab and split panes, detachable panels and toolbars
-
Detailed logging of client actions
3.3. Architecture
3.3.3. Assembly
EntityModel
/**
* Creates a SwingEntityModel based on the {@link Artist#TYPE} entity
* with a detail model based on {@link Album#TYPE}
* @param connectionProvider the connection provider
*/
static SwingEntityModel artistModel(EntityConnectionProvider connectionProvider) {
// initialize a default edit model
SwingEntityEditModel artistEditModel = new SwingEntityEditModel(Artist.TYPE, connectionProvider);
// initialize a default table model
SwingEntityTableModel artistTableModel = new SwingEntityTableModel(Artist.TYPE, connectionProvider);
// initialize a default model using the edit and table models
SwingEntityModel artistModel = new SwingEntityModel(artistEditModel, artistTableModel);
// Note that this does the same as the above, that is, initializes
// a SwingEntityModel with a default edit and table model
SwingEntityModel albumModel = new SwingEntityModel(Album.TYPE, connectionProvider);
artistModel.addDetailModel(albumModel);
return artistModel;
}
EntityPanel
/**
* Creates a EntityPanel based on the {@link Artist#TYPE} entity
* with a detail panel based on {@link Album#TYPE}
* @param connectionProvider the connection provider
*/
static EntityPanel artistPanel(EntityConnectionProvider connectionProvider) {
// initialize the EntityModel to base the panel on
SwingEntityModel artistModel = artistModel(connectionProvider);
// fetch the edit model
SwingEntityEditModel artistEditModel = artistModel.getEditModel();
// fetch the table model
SwingEntityTableModel artistTableModel = artistModel.getTableModel();
// fetch the album detail model
SwingEntityModel albumModel = artistModel.getDetailModel(Album.TYPE);
// create a EntityEditPanel instance, based on the artist edit model
EntityEditPanel artistEditPanel = new EntityEditPanel(artistEditModel) {
@Override
protected void initializeUI() {
createTextField(Artist.NAME).setColumns(15);
addInputPanel(Artist.NAME);
}
};
// create a EntityTablePanel instance, based on the artist table model
EntityTablePanel artistTablePanel = new EntityTablePanel(artistTableModel);
// create a EntityPanel instance, based on the artist model and
// the edit and table panels from above
EntityPanel artistPanel = new EntityPanel(artistModel, artistEditPanel, artistTablePanel);
// create a new EntityPanel, without an edit panel and
// with a default EntityTablePanel
EntityPanel albumPanel = new EntityPanel(albumModel);
artistPanel.addDetailPanel(albumPanel);
return artistPanel;
}
3.3.4. Full Example
Show code
package is.codion.framework.demos.chinook.tutorial;
import is.codion.common.db.database.Database;
import is.codion.common.db.database.DatabaseFactory;
import is.codion.common.user.User;
import is.codion.framework.db.EntityConnectionProvider;
import is.codion.framework.db.local.LocalEntityConnectionProvider;
import is.codion.framework.demos.chinook.domain.Chinook.Album;
import is.codion.framework.demos.chinook.domain.Chinook.Artist;
import is.codion.framework.demos.chinook.domain.impl.ChinookImpl;
import is.codion.swing.framework.model.SwingEntityEditModel;
import is.codion.swing.framework.model.SwingEntityModel;
import is.codion.swing.framework.model.SwingEntityTableModel;
import is.codion.swing.framework.ui.EntityEditPanel;
import is.codion.swing.framework.ui.EntityPanel;
import is.codion.swing.framework.ui.EntityTablePanel;
/**
* When running this make sure the chinook demo module directory is the
* working directory, due to a relative path to a db init script
*/
public final class ClientArchitecture {
// tag::entityModel[]
/**
* Creates a SwingEntityModel based on the {@link Artist#TYPE} entity
* with a detail model based on {@link Album#TYPE}
* @param connectionProvider the connection provider
*/
static SwingEntityModel artistModel(EntityConnectionProvider connectionProvider) {
// initialize a default edit model
SwingEntityEditModel artistEditModel = new SwingEntityEditModel(Artist.TYPE, connectionProvider);
// initialize a default table model
SwingEntityTableModel artistTableModel = new SwingEntityTableModel(Artist.TYPE, connectionProvider);
// initialize a default model using the edit and table models
SwingEntityModel artistModel = new SwingEntityModel(artistEditModel, artistTableModel);
// Note that this does the same as the above, that is, initializes
// a SwingEntityModel with a default edit and table model
SwingEntityModel albumModel = new SwingEntityModel(Album.TYPE, connectionProvider);
artistModel.addDetailModel(albumModel);
return artistModel;
}
// end::entityModel[]
// tag::entityPanel[]
/**
* Creates a EntityPanel based on the {@link Artist#TYPE} entity
* with a detail panel based on {@link Album#TYPE}
* @param connectionProvider the connection provider
*/
static EntityPanel artistPanel(EntityConnectionProvider connectionProvider) {
// initialize the EntityModel to base the panel on
SwingEntityModel artistModel = artistModel(connectionProvider);
// fetch the edit model
SwingEntityEditModel artistEditModel = artistModel.getEditModel();
// fetch the table model
SwingEntityTableModel artistTableModel = artistModel.getTableModel();
// fetch the album detail model
SwingEntityModel albumModel = artistModel.getDetailModel(Album.TYPE);
// create a EntityEditPanel instance, based on the artist edit model
EntityEditPanel artistEditPanel = new EntityEditPanel(artistEditModel) {
@Override
protected void initializeUI() {
createTextField(Artist.NAME).setColumns(15);
addInputPanel(Artist.NAME);
}
};
// create a EntityTablePanel instance, based on the artist table model
EntityTablePanel artistTablePanel = new EntityTablePanel(artistTableModel);
// create a EntityPanel instance, based on the artist model and
// the edit and table panels from above
EntityPanel artistPanel = new EntityPanel(artistModel, artistEditPanel, artistTablePanel);
// create a new EntityPanel, without an edit panel and
// with a default EntityTablePanel
EntityPanel albumPanel = new EntityPanel(albumModel);
artistPanel.addDetailPanel(albumPanel);
return artistPanel;
}
// end::entityPanel[]
public static void main(final String[] args) {
// Configure the database
Database.DATABASE_URL.set("jdbc:h2:mem:h2db");
Database.DATABASE_INIT_SCRIPTS.set("src/main/sql/create_schema.sql");
// initialize a connection provider, this class is responsible
// for supplying a valid connection or throwing an exception
// in case a connection can not be established
EntityConnectionProvider connectionProvider =
new LocalEntityConnectionProvider(DatabaseFactory.getDatabase())
.setDomainClassName(ChinookImpl.class.getName())
.setUser(User.parseUser("scott:tiger"));
final EntityPanel artistPanel = artistPanel(connectionProvider);
// lazy initialization of the UI
artistPanel.initializePanel();
// fetch data from the database
artistPanel.getModel().refresh();
// uncomment the below line to display the panel
// displayInDialog(null, artistPanel, "Artists");
connectionProvider.close();
}
}
4. Server
4.1. Features
-
Firewall friendly; uses one way communications without callbacks and can be configured to serve on a single fixed port
-
Integrated web server for serving Web Start applications and files, based on Jetty
-
All user authentication left to the database by default
-
Comprehensive administration and monitoring facilities via the ServerMonitor
-
Featherweight server with moderate memory and CPU usage
4.2. Security
4.2.1. Authentication
The Codion server does not perform any user authentication by default, it leaves that up the the underlying database. An authentication layer can be added by implementing a LoginProxy and setting the following system property.
codion.server.loginProxyClasses=com.app.server.AppLoginProxy # (1)
-
A comma separated list of login proxies
4.2.2. SSL encryption
To enable SSL encryption between client and server, create a keystore and truststore pair and set the following system properties.
4.2.4. Serialization whitelist
A serialization whitelist can be used by setting the following system property.
codion.server.serializationFilterWhitelist=config/whitelist.txt
A whitelist can be created during a server dry-run by adding the following system property. The whitelist containing all classes deserialized during the run is written to disk on server shutdown.
codion.server.serializationFilterDryRun=true
Example whitelist
[B
[C
[Lis.codion.framework.domain.entity.Attribute;
[Ljava.lang.Object;
[Ljava.lang.String;
[Ljava.util.Map$Entry;
[Lnet.sf.jasperreports.engine.JRBand;
[Lnet.sf.jasperreports.engine.JRExpressionChunk;
[Lnet.sf.jasperreports.engine.JRField;
[Lnet.sf.jasperreports.engine.JRParameter;
[Lnet.sf.jasperreports.engine.JRQueryChunk;
[Lnet.sf.jasperreports.engine.JRVariable;
ch.qos.logback.classic.Level
java.lang.Boolean
java.lang.Character
java.lang.Double
java.lang.Enum
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Number
java.lang.String
java.math.BigDecimal
java.math.BigInteger
java.time.LocalDate
java.time.LocalDateTime
java.time.LocalTime
java.time.Ser
java.util.ArrayList
java.util.Arrays$ArrayList
java.util.Collections$EmptyList
java.util.Collections$SingletonList
java.util.Collections$SingletonMap
java.util.Collections$UnmodifiableCollection
java.util.Collections$UnmodifiableList
java.util.Collections$UnmodifiableRandomAccessList
java.util.Date
java.util.HashMap
java.util.HashSet
java.util.LinkedHashMap
java.util.UUID
net.sf.jasperreports.compilers.ConstantExpressionEvaluation
net.sf.jasperreports.compilers.FieldEvaluation
net.sf.jasperreports.compilers.ReportExpressionEvaluationData
net.sf.jasperreports.engine.JRPropertiesMap
net.sf.jasperreports.engine.JasperReport
net.sf.jasperreports.engine.base.JRBaseBand
net.sf.jasperreports.engine.base.JRBaseBoxBottomPen
net.sf.jasperreports.engine.base.JRBaseBoxLeftPen
net.sf.jasperreports.engine.base.JRBaseBoxPen
net.sf.jasperreports.engine.base.JRBaseBoxRightPen
net.sf.jasperreports.engine.base.JRBaseBoxTopPen
net.sf.jasperreports.engine.base.JRBaseDataset
net.sf.jasperreports.engine.base.JRBaseElement
net.sf.jasperreports.engine.base.JRBaseElementGroup
net.sf.jasperreports.engine.base.JRBaseExpression
net.sf.jasperreports.engine.base.JRBaseExpressionChunk
net.sf.jasperreports.engine.base.JRBaseField
net.sf.jasperreports.engine.base.JRBaseLineBox
net.sf.jasperreports.engine.base.JRBaseParagraph
net.sf.jasperreports.engine.base.JRBaseParameter
net.sf.jasperreports.engine.base.JRBasePen
net.sf.jasperreports.engine.base.JRBaseQuery
net.sf.jasperreports.engine.base.JRBaseQueryChunk
net.sf.jasperreports.engine.base.JRBaseReport
net.sf.jasperreports.engine.base.JRBaseSection
net.sf.jasperreports.engine.base.JRBaseStaticText
net.sf.jasperreports.engine.base.JRBaseTextElement
net.sf.jasperreports.engine.base.JRBaseTextField
net.sf.jasperreports.engine.base.JRBaseVariable
net.sf.jasperreports.engine.design.JRReportCompileData
net.sf.jasperreports.engine.type.CalculationEnum
net.sf.jasperreports.engine.type.EvaluationTimeEnum
net.sf.jasperreports.engine.type.HorizontalTextAlignEnum
net.sf.jasperreports.engine.type.IncrementTypeEnum
net.sf.jasperreports.engine.type.OrientationEnum
net.sf.jasperreports.engine.type.PositionTypeEnum
net.sf.jasperreports.engine.type.PrintOrderEnum
net.sf.jasperreports.engine.type.ResetTypeEnum
net.sf.jasperreports.engine.type.RunDirectionEnum
net.sf.jasperreports.engine.type.SectionTypeEnum
net.sf.jasperreports.engine.type.SplitTypeEnum
net.sf.jasperreports.engine.type.StretchTypeEnum
net.sf.jasperreports.engine.type.TextAdjustEnum
net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum
is.codion.common.Conjunction
is.codion.common.db.Operator
is.codion.common.db.operation.DefaultFunctionType
is.codion.common.db.operation.DefaultProcedureType
is.codion.common.db.reports.AbstractReport
is.codion.common.db.reports.DefaultReportType
is.codion.common.rmi.client.DefaultConnectionRequest
is.codion.common.user.DefaultUser
is.codion.common.version.DefaultVersion
is.codion.framework.db.condition.AbstractCondition
is.codion.framework.db.condition.DefaultConditionCombination
is.codion.framework.db.condition.DefaultCustomCondition
is.codion.framework.db.condition.DefaultSelectCondition
is.codion.framework.db.condition.DefaultUpdateCondition
is.codion.framework.db.condition.AbstractAttributeCondition
is.codion.framework.db.condition.DefaultAttributeEqualCondition
is.codion.framework.db.condition.DefaultAttributeGreaterThanCondition
is.codion.framework.db.condition.DefaultAttributeLessThanCondition
is.codion.framework.db.condition.DefaultAttributeBetweenCondition
is.codion.framework.db.condition.DefaultAttributeNotBetweenCondition
is.codion.framework.db.condition.Conditions$EmptyCondition
is.codion.framework.domain.DefaultDomainType
is.codion.framework.domain.entity.DefaultConditionType
is.codion.framework.domain.entity.DefaultEntity
is.codion.framework.domain.entity.DefaultEntityType
is.codion.framework.domain.entity.DefaultForeignKey
is.codion.framework.domain.entity.DefaultForeignKey$DefaultReference
is.codion.framework.domain.entity.DefaultKey
is.codion.framework.domain.entity.DefaultOrderBy
is.codion.framework.domain.entity.DefaultOrderBy$DefaultOrderByAttribute
is.codion.framework.domain.entity.DefaultAttribute
is.codion.framework.domain.entity.Entity
is.codion.framework.demos.chinook.domain.Chinook
is.codion.framework.demos.chinook.domain.Chinook$Invoice
is.codion.framework.demos.chinook.domain.Chinook$Track
is.codion.framework.demos.empdept.domain.EmpDept
is.codion.framework.demos.world.domain.api.*
is.codion.framework.demos.empdept.domain.*
is.codion.plugin.jasperreports.model.DefaultJRReportType
4.2.5. Security Policy
Security policy generated with ProGrade.
Requires this fix: Issue#29
Demo security policy
grant {
permission java.io.FilePermission "../config/-", "read";
permission java.io.FilePermission "../lib/-", "read";
permission java.io.FilePermission "../logs/-", "read,write,delete";
permission java.io.FilePermission "../reports/-", "read";
permission java.io.FilePermission "../web", "read";
permission java.io.FilePermission "jasperreports.properties", "read";
permission java.io.FilePermission "mem:h2db.h2.db", "read";
permission java.io.FilePermission "mem:h2db.mv.db", "read";
permission java.io.FilePermission "net/sf/jasperreports/fonts/icons/icons.ttf", "read";
permission java.io.FilePermission "net/sf/jasperreports/fonts/jasperreports-fonts.xml", "read";
permission java.io.FilePermission "/tmp/*", "write,delete";
permission java.lang.management.ManagementPermission "monitor";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "createClassLoader";
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "getenv.JETTY_AVAILABLE_PROCESSORS";
permission java.lang.RuntimePermission "getenv.JETTY_WORKER_INSTANCE";
permission java.lang.RuntimePermission "getProtectionDomain";
permission java.lang.RuntimePermission "setContextClassLoader";
permission java.lang.RuntimePermission "shutdownHooks";
permission java.net.SocketPermission "127.0.0.1:*", "accept,resolve";
permission java.net.SocketPermission "localhost:2222", "listen,resolve";
permission java.net.SocketPermission "localhost:4444", "listen,resolve";
permission java.net.SocketPermission "localhost:8080", "listen,resolve";
permission java.util.PropertyPermission "java.rmi.server.hostname", "write";
permission java.util.PropertyPermission "java.rmi.server.randomIDs", "write";
permission java.util.PropertyPermission "java.rmi.server.useCodebaseOnly", "write";
permission java.util.PropertyPermission "javax.net.ssl.keyStorePassword", "write";
permission java.util.PropertyPermission "javax.net.ssl.keyStore", "write";
permission java.util.PropertyPermission "javax.net.ssl.trustStorePassword", "write";
permission java.util.PropertyPermission "javax.net.ssl.trustStore", "write";
permission java.util.PropertyPermission "jetty.git.hash", "write";
permission java.util.PropertyPermission "codion.*", "write";
permission java.util.PropertyPermission "logback.configurationFile", "write";
permission java.util.PropertyPermission "*", "read,write";
};
4.3. Configuration
4.3.1. Example configuration file
# Database configuration
codion.db.url=jdbc:h2:mem:h2db
codion.db.useOptimisticLocking=true
codion.db.initScripts=\
../config/empdept/create_schema.sql,\
../config/chinook/create_schema.sql,\
../config/petstore/create_schema.sql,\
../config/world/create_schema.sql
# The admin user credentials, used by the server monitor application
codion.server.admin.user=scott:tiger
# Client logging enabled by default
codion.server.clientLoggingEnabled=true
# A connection pool based on this user is created on startup
codion.server.pooling.startupPoolUsers=scott:tiger
# The port used by clients
codion.server.port=2222
# The port for the admin interface, used by the server monitor
codion.server.admin.port=4444
# RMI Registry port
codion.server.registryPort=1099
# Any auxiliary servers to run along this server
codion.server.auxiliaryServerFactoryClassNames=\
is.codion.framework.servlet.EntityServletServerFactory
# A directory from which to serve files
codion.server.http.documentRoot=../web
# The http port
codion.server.http.port=8080
# Specifies whether or not to use https
codion.server.http.secure=false
# The serialization whitelist to use for RMI deserialization
codion.server.serializationFilterWhitelist=\
../config/serialization-whitelist.txt
# RMI configuration
java.rmi.server.hostname=localhost
java.rmi.server.randomIDs=true
java.rmi.server.useCodebaseOnly=true
# SSL configuration
javax.net.ssl.keyStore=../config/keystore.jks
javax.net.ssl.keyStorePassword=crappypass
# Used to connect to the server to shut it down
javax.net.ssl.trustStore=../config/truststore.jks
javax.net.ssl.trustStorePassword=crappypass
4.4. Code examples
Absolute bare bones examples of how to run the EntityServer and connect to it.
4.4.1. RMI
Database database = new H2DatabaseFactory()
.createDatabase("jdbc:h2:mem:testdb",
"src/main/sql/create_schema.sql");
EntityServerConfiguration configuration = EntityServerConfiguration.configuration(SERVER_PORT, REGISTRY_PORT);
configuration.setDomainModelClassNames(singletonList(Store.class.getName()));
configuration.setDatabase(database);
configuration.setSslEnabled(false);
EntityServer server = EntityServer.startServer(configuration);
RemoteEntityConnectionProvider connectionProvider =
new RemoteEntityConnectionProvider("localhost", SERVER_PORT, REGISTRY_PORT);
connectionProvider.setDomainClassName(Store.class.getName());
connectionProvider.setUser(parseUser("scott:tiger"));
connectionProvider.setClientTypeId("ClientServer");
EntityConnection connection = connectionProvider.getConnection();
List<Entity> customers = connection.select(condition(Customer.TYPE));
customers.forEach(System.out::println);
connection.close();
server.shutdown();
4.4.2. HTTP
Database database = new H2DatabaseFactory()
.createDatabase("jdbc:h2:mem:testdb",
"src/main/sql/create_schema.sql");
EntityServerConfiguration configuration = EntityServerConfiguration.configuration(SERVER_PORT, REGISTRY_PORT);
configuration.setDomainModelClassNames(singletonList(Store.class.getName()));
configuration.setDatabase(database);
configuration.setSslEnabled(false);
configuration.setAuxiliaryServerFactoryClassNames(singletonList(EntityServletServerFactory.class.getName()));
HttpServerConfiguration.HTTP_SERVER_PORT.set(HTTP_PORT);
HttpServerConfiguration.HTTP_SERVER_SECURE.set(ServerHttps.FALSE);
EntityServer server = EntityServer.startServer(configuration);
HttpEntityConnectionProvider connectionProvider =
new HttpEntityConnectionProvider("localhost", HTTP_PORT, ClientHttps.FALSE);
connectionProvider.setDomainClassName(Store.class.getName());
connectionProvider.setUser(parseUser("scott:tiger"));
connectionProvider.setClientTypeId("ClientServer");
EntityConnection connection = connectionProvider.getConnection();
List<Entity> customers = connection.select(condition(Customer.TYPE));
customers.forEach(System.out::println);
connection.close();
server.shutdown();
5. Server Monitor
The Codion Server Monitor provides a way to monitor the Codion remote server.
Below are screenshots of the different server monitor tabs, after ~1 1/2 hours of running the Chinook load test, with ~5 minutes of ramping up. The server is running on a Raspberry Pi 4, Ubuntu 18.04, JDK 14, using a HikariCP connection pool on top of an H2 in-memory database.
6. Modules
6.1. Common
Common classes used throughout the framework.
codion-common-core
Dependency graph
codion-common-db
Dependency graph
codion-common-model
Dependency graph
codion-common-rmi
Dependency graph
codion-common-http
Dependency graph
6.2. DBMS
Database specific implementation classes.
codion-dbms-derby
Dependency graph
codion-dbms-h2database
Dependency graph
codion-dbms-hsqldb
Dependency graph
codion-dbms-mariadb
Dependency graph
codion-dbms-mysql
Dependency graph
codion-dbms-oracle
Dependency graph
codion-dbms-postgresql
Dependency graph
codion-dbms-sqlite
Dependency graph
codion-dbms-sqlserver
Dependency graph
6.3. Framework
The framework itself.
codion-framework-domain
Dependency graph
codion-framework-domain-test
Dependency graph
codion-framework-db-core
Dependency graph
codion-framework-db-local
Dependency graph
codion-framework-db-rmi
Dependency graph
codion-framework-db-http
Dependency graph
codion-framework-model
Dependency graph
codion-framework-model-test
Dependency graph
codion-framework-server
Dependency graph
codion-framework-servlet
Dependency graph
6.4. Swing
Swing client implementation.
codion-swing-common-model
Dependency graph
codion-swing-common-ui
Dependency graph
codion-swing-common-tools
Dependency graph
codion-swing-common-tools-ui
Dependency graph
codion-swing-framework-model
Dependency graph
codion-swing-framework-ui
Dependency graph
codion-swing-framework-ui-test
Dependency graph
codion-swing-framework-tools
Dependency graph
codion-swing-framework-tools-ui
Dependency graph
codion-swing-framework-server-monitor
Dependency graph
6.5. JavaFX
JavaFX client implementation (still quite experimental).