J-Framework Core
Most likely, you will NOT need to add the direct dependency to this project, since its already included in the parent projects or the other framework projects. However, if you need to, the dependency is
<!-- https://mvnrepository.com/artifact/com.jalalkiswani/j-framework-core -->
<dependency>
<groupId>com.jalalkiswani</groupId>
<artifactId>j-framework-core</artifactId>
<version>7.0.1</version>
</dependency>
The key components of the Core projects are:
-
Sophisticated Configuration API which is API that covers the configuration needs of software systems on all scales.
-
Easy to use and efficient Exception Handling API, and set of custom exceptions that could be required in most applications.
-
Context API that enable sharing context information across the same JVM and across microrservices transparently.
-
Resource Loading API which solve the challenge of resources-loading from: classpath, , file system, current working directory, or inside web-application.
-
Remote Reflection API which allows Java reflection over TCP/IP on remote server.
-
Caching API that supports local in-memory cache based on simple implementation in addition to a Guava wrapper.
-
Logging API wrappers around Logback.
-
<<core-version>
-
Also, It includes some utilities and wrappers such as:
-
Template API based on FreeMarker.
-
Reflection, IO, Collections, Strings, Excel, Conversion, Validation, and many others.
-
Git wrapper to JGit to enable easier integration with a Git repository.
-
Java compiler and command line utilities.
-
Collections utils such as JKListHolder which enables a graph-like implementation of lists.
-
E-Mail sending utilities.
-
SSH Client wrapper around JSch.
-
Timer utility to execute task within specific time frame.
-
Factory API based on Spring framework.
-
Wrapper to ThreadLocal
-
Utility to handle Arabic numbers,
-
Test Automation Stuff (In progress)
-
Security stuff.
-
Check the API documentation for more information.
Configuration API
Configuration API in J-Framework is an easy-to-use, flexible,and sophisticated config API’s that enables an efficient approach of managing the configuration of software systems on all scales. It could allow configurations on component level, system level, with support of classpath configurations, local directory, git, and others.
Configuration of software systems is not an easy task, since new trends, practices, and technologies are demanding a higher level of operation automation through proper DevOps and agile practices, where, for example, changing the data-source password for different environments (dev, test, staging, and prod) shouldn’t be that hard and time-consuming, and should happens in an efficient approach and shall takes near-immediate effect on the systems with zero-downtime.
Use-Cases
Below are some use-cases to use the config API:
Dev Environment:
-
For simple web application Development environment: Add
config.properties
to yoursrc/main/resources
folder. -
For simple standalone applications: Add
smartcloud.config
to your current working directory. -
For Multi-module applications such as microservices and micro-fronts:
-
Create a
git
repository withdev
branch, and createsystem.config.properties
file which shall contains the common config for all of your systems, in addition, create APP_NAME.config.properties which shall contains the config of specific app. -
Create and place
pre.config.properties
file in the commons-library of your system, which shall contains the config needed to access thegit
repository. -
In each project, add
src/main/resources/app.config.properties
which shall contains only keyapp.name=YOUR_APP_NAME
.
Thats it, now all the configurations are managed in a git repository, and config could be reloaded instantly by hitting a/util/config
endpoint of your component usingpost
method.
-
Tests, Staging, and Prod Environments:
-
Follow the same approach above.
-
In the CICD process, replace the config files with the new one that contains the target environment configurations.
-
For security and operational purposes, you could place the critical configurations such as passwords and encryption keys as environment variables on the target host machine, and reference it using the
env:
prefix likeyour-config-key= ${env:YOUR-CONFIG-KEY}
.
Basic Usage
In your Maven project, create src/main/resources/config.properties
file, with the following sample contents:
app.name = J-Framework Demo
hibernate.connection.url = jdbc:h2:file:./h2db.data
hibernate.connection.username = sa
hibernate.connection.password = sa
db-entities-packages=com.app
Properties can be retrieved with:
JKConfig.get().getProperty(propertyName);
or with
JK.getProperty(propertyName);
As a shortcut.
Config Initialization
In most cases, configuration will be auto unidolized and loaded during the application
startups using listeners. However, if for some reason you want to initialize it your self,
the first call to JKConfig.getDefaultInstance()
will auto configure it.
And to read value from the configuration, use the following code:
String property = JKConfig.get().getProperty("your-variable-name", "default value);
When password started with three under scores "_", the password will be considered encrypted, the decryption algorithm will remove the three hashes then decrypts the password. |
Configuration Order
In J-Framework, the configuration are loaded from the following config sources based on the priorities-order:
-
Load
jk.config
file if exists from current working directory, and override any configuration from the following config sources (useful for standalone deployments). -
Load
test.config.properties
file if exists from the classpath, and override any configuration from the following config sources (useful for test automation). -
Process
pre.config.properties
which shall contains a local path for the config and/or Git repository settings and overrider and properties configured in the later config sources (useful for Microservices and Multi-components systems). In this approach:-
Look for app.name property, if not found, look for it in the config sources.
-
If config.local.path is available, read
system.config.properties
and${app.name}.config.properties
from it. -
if git-url is there, repository will be cloned to local folder config.local.path value if configured.
-
Read
system.config.properties
andYOUR_APP_NAME.config.properties
from cloned repository. -
If git-keep-local property is false (default), the cloned local folder will be deleted once loaded.
-
-
Load JKFRAMEWORK_ENV key from system environment variable if exists, where the value is CSV key-value format of the configuration, and override any of the following configurations (useful for apps with simple configurations which requires host operator ownership).
-
Load configuration from
config.properties
and override any of the following configurations. -
Load configuration from
app.config.properties
and override any of the following configurations. -
Load configuration from
system.config.properties
and override any of the following configurations. -
Load configuration from
default.config.properties
(not recommended to use, since its loaded from the framework Data API’s).
Load Configuration from Local Folder or Git Repository
For advanced configurations, you can specify a Git repository to host your configurations per system, per app, or even per environment.
You can do it by adding a pre.config.properties to your src/main/resources folder of your project.
app.name=....
config.local.path=....
git-url=....
git-username=....
#git-password=.....
git-password-plain=.....
In your repository, you can have system.config.properties
file as default configuration
for all the apps in your applications, also you can specify the configuration per module by
having a file named APP_NAME.config.properties
where APP_NAME is the value of
your app.name property in the pre.config.properties
.
mentioned above.
For profiles, you can use git branches as profiles, for example, you can create dev, test, staging, and prod branches.
J-Framework configuration is based on Apache Commons Config, so you can use the system, constants, or environment variables as follows:
user.file = ${sys:user.home}/settings.xml action.key = ${const:yourclass.CONSTANT_NAME} java.home = ${env:JAVA_HOME}
Available Configuration properties [core-config-all]
This section shows list of available configs properties used by J-Framework.
Generic Config
Header Name |
Default |
Description |
app.name |
app |
Your application name |
jk.security.enc-key |
SetMeIneEnvVaria |
Encryption key used in encryption and decryption |
Data Configurations
Below are the configuration and their defaults of the Data project, including database, nosql, and connection pool settings;
Detailed configuration of the c3p0 connection pool available at C3p0 Official Documentaion |
Config Name | Default Value | Description |
---|---|---|
nosql.url |
mongodb://localhost:27017/ |
- |
nosql.db.name |
app |
Default Database name. |
nosql.db.user |
- |
Default Database user. |
nosql.db.password |
Default Database password. |
|
db-entities-packages (deprecated) |
com.app |
The package names to scan for JPA entities. CSV value is allowed for multiple packages |
jk.data.jpa.entities-packages |
com.app |
Starting from 7.0.0-M6, The package names to scan for JPA entities. CSV value is allowed for multiple packages |
jk.data.connection.set_client_info |
false |
Set client information on the JDBC connection, helpful for DBA’s and troubleshooting |
hibernate.connection.url |
jdbc:h2:file:~/.jk/h2-db/h2db2.data;IGNORECASE=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DATABASE_TO_UPPER=FALSE; |
Database URL |
hibernate.connection.username |
sa |
Database username. |
hibernate.connection.password |
sa |
Database password. |
hibernate.connection.driver_class |
- |
Database driver, most likely you will not need to set it+, it will be automatically detected by the framework. |
hibernate.dialect |
- |
Hibernate database database dialect, most likely you will not need to set |
hibernate.c3p0.max_size |
100 |
Maximum number of database connections in the pool. |
hibernate.c3p0.min_size |
1 |
Minimum number of database connections that the pool keeps |
hibernate.physical_naming_strategy |
- |
Naming strategy to be used for mapping with database |
hibernate.id.new_generator_mappings |
false |
- |
hibernate.globally_quoted_identifiers |
true |
- |
hibernate.use_sql_comments |
- |
- |
hibernate.format_sql |
true |
Format the generated SQL statement. |
hibernate.show_sql |
true |
Show Generated SQL statement. |
hibernate.connection.autocommit |
true |
Default to Auto Commit. |
hibernate.hbm2ddl.auto |
update |
Create tables of not exists. |
hibernate.c3p0.contextClassLoaderSource |
library |
- |
hibernate.c3p0.privilegeSpawnedThreads |
true |
- |
hibernate.c3p0.debugUnreturnedConnectionStackTraces |
true |
|
- |
hibernate.c3p0.unreturnedConnectionTimeout |
10 |
Defines a limit (in seconds) to how long a connection may remain checked out. |
hibernate.c3p0.acquireRetryDelay |
10000 |
Millis |
hibernate.c3p0.acquireRetryAttempts |
1 |
- |
hibernate.c3p0.idleConnectionTestPeriod |
30 |
Seconds |
hibernate.c3p0.preferredTestQuery |
- |
- |
hibernate.c3p0.testConnectionOnCheckout |
false |
- |
hibernate.c3p0.maxConnectionAge |
18000 |
#Refresh the connection anyways after this timeout (in seconds) |
hibernate.c3p0.timeout |
600 |
Release un-needed connections (in seconds) above the min-size |
hibernate.c3p0.acquire_increment |
1 |
- |
hibernate.c3p0.checkoutTimeout |
20000 |
Millis |
hibernate.c3p0.maxStatements |
0 |
Unlimited |
hibernate.c3p0.maxStatementsPerConnection |
10 |
- |
jk.data.orm.results.max |
1000 |
- |
hibernate.c3p0.numHelperThreads |
10 |
- |
jk.data.datasource.impl |
hibernate |
Git Config
Config Name | Default Value | Description |
---|---|---|
git-url |
- |
Repository URL of config repository. |
git-keep-local |
false |
Keep/Remove cloned repository folder. |
git-branch |
master |
Target git branch. |
git-password-plain |
- |
Plain password or token of the repository (Not recommended) |
git-password |
- |
Encrypted password of your git account. |
git-username |
- |
Usename of you git repository. |
git-local-path |
- |
The path where your repository should be cloned. |
Microservices Config
Config Name | Default | Description |
---|---|---|
jk.service.url.visibleOnError |
true |
Shall the framework show the remote microservice URL on error (recommanded to be off on production) |
jk.service.allowed.ip |
true |
CSV List of ip’s that are allowed to call a microservice. |
jk.service.connect_timeout |
10 |
Allowed timeout in seconds to connect to a microservice |
jk.service.read_timeout |
20 |
Allowed timeout in seconds to wait response from microservice |
jk.services.headers.enabled |
true |
Allow showing request header on a microservice (Recommended to be false on production) |
jk.config.allowReload |
true |
Allow reload config on runtime for a microservice or microfront end |
jk.services.info.enabled |
true |
Allow showing microservice info (Swagger like) of microservice (Recommanded to be off on production) |
jk.logs.allowReadFile |
false |
Allow reading microservice log file remotly. Even though passwords will be masked, it is recommended to be turned off on production. |
jk.config.allowReadConfig |
true |
Allow reload configuration at runtime. |
jk.service.crosscutting.logservice.enabled |
false |
Check whether crosscutting remote calls should be logged to the unified logging microservice |
jk.service.crosscutting.logservice.base |
- |
The base url for the unified logging service |
jk.service.crosscutting.logservice.async |
true |
Shall unified loggging be called async or sync |
jk.services.workflow.url |
- |
The base url of the workflow engine. |
Web Config
Header Name | Default | Description |
---|---|---|
jk.security.enabled |
false |
Enable security, the default is based on Spring Security. |
jk.web.security.public_url |
/services/, /index.xhtml, /error/, /login/, /public/, /resources/ , .css, .js, /jakarta.faces.resource/, /javax.faces.resource/, /util/ |
List of URLs the considered public by default. |
jk.web.session.username |
- |
If managed by the application, the name of session attribute variable that holds the username. |
jk.web.config.reset |
true |
Servlet URL that allows reset config |
jk.web.versions |
true |
Servlet URL that shows the versions of the framework components |
jk.web.mvc.models.package |
com.app |
(an MVC framework, in Progress) |
Exception Handling API
Exception handling in the framework is implemented based on my article Exception Handling in Real-Life Java Applications published on DZone.com.
The main concepts about exception handling:
-
If you have checked exception, always have only one catch statement.
-
Never catch Runtime exception
-
In the catch statement just call
JK.throww(e);
the framework will handle the rest.-
In web applications, the exception will be logged then redirected to error page.
-
In AJAX requests, the exception will be logged then shown as error message to to the users.
-
The exception will be redirected to the proper registered exception handler.
-
-
Use
JK.exception("message")
to throw an exception. -
If you are handling an exception inside method that should return value, it is safe to return null after
JK.throw(e)
.
Example 1:
package com.app;
import com.jk.util.JK;
public class ExceptionExample1 {
public static void main(String[] args) {
//Throw nullpointer exception and handle with default handler, which log the error then through runtime exception
try {
String name = null;
name.length();
} catch (Exception e) {
JK.handle(e);
}
}
}
Custom Exceptions Handling
-
Create Custom Handler
package com.app; import javax.swing.JOptionPane; import com.jk.util.exceptions.handler.ExceptionHandler; import com.jk.util.exceptions.handler.JKExceptionHandler; @ExceptionHandler public class ArrayIndexExceptionHandler implements JKExceptionHandler<ArrayIndexOutOfBoundsException> { @Override public void handle(ArrayIndexOutOfBoundsException throwable, boolean throwRuntimeException) { JOptionPane.showMessageDialog(null, "My ArrayIndexException Handler"); } }
-
Register Handler:
package com.app;
import com.jk.util.JK;
import com.jk.util.exceptions.handler.JKExceptionHandlerFactory;
public class ExceptionExample2 {
public static void main(String[] args) {
JKExceptionHandlerFactory.getInstance().registerHandlers("com.app");
//Throw nullpointer exception and handle with custom handler, which show the message in JOptionPane
try {
int arr[]=new int[0];
int x=arr[1];
} catch (Exception e) {
JK.handle(e);
}
}
}
Remote Reflection
Remote Reflection is a Java API which make it easier to call remote methods on remote JVM in very simple way without the need for any special configurations or common contract between client and server.
Test Service
package com.app.server;
public class TestService {
public String hello(String name) {
return "Hello " + name + " from server";
}
}
Server
In the server application(the application which you want to expose its method remotely) , add the following after at the end of you main method:
package com.app.server;
import com.jk.util.reflection.server.ReflectionServer;
public class ServiceServer {
public static void main(String[] args) {
int port=7125;
ReflectionServer server = new ReflectionServer(port);
server.start();
}
}
Thats it , now you can expose any method inside this application VM to your application client.
Client
In the client application(the application which should consume the remote method):
package com.app.client;
import com.jk.util.reflection.client.ReflectionClient;
import com.jk.util.reflection.common.MethodCallInfo;
public class ServiceClient {
public static void main(String[] args) {
ReflectionClient client=new ReflectionClient("localhost", 7125);
MethodCallInfo info=new MethodCallInfo("com.app.server.TestService", "hello", "Jalal");
client.callMethod(info);
String result = (String) info.getResult();
System.out.println(result);
}
}
Monitoring
-
On exception on a microservice: the services will publish the error to the logging service
-
On exception on a microservice client: the client will publish the error to the logging service
-
On j-framework-web with service client, scenario number 2 will apply
-
on j-framework-webstack, exceptions will be logged to "mon_events" database table
-
to register custom publisher: JKMonitorService.setInstance(new JKWebStackMonitor());
To call log service manually:
JKLogServiceClient logService = new JKLogServiceClient();
logService.callError(exception.getMessage());
Caching API
package com.app.cache;
import com.jk.core.cache.JKCacheFactory;
import com.jk.core.cache.JKCacheManager;
import com.jk.core.util.JK;
public class JKCacheExample {
public static void main(String[] args) {
//Cache value
JKCacheManager cm = JKCacheFactory.getCacheManager();
cm.cache("username", "Jalal");
//Retrieve value
String value = cm.get("username", String.class);
JK.print(value);
//Remove value
cm.remove("username", String.class);
String value2 = cm.get("username", String.class);
JK.print(value2);//null
}
}
Logging API
This section includes the logging description in the framework, which is currently based on Logback and Slf4J.
Always consider adding logging to classes that could require debugging or monitoring. |
Basic Usage
You can use logging by JKLogger logger=JKLoggerFactory.getLogger(getClass());
public class YourClass{
JKLogger logger=JKLoggerFactory.getLogger(getClass());
...
public void method(String param1){
logger.info("method1() with param {}",param1);
....
...
logger.info("\method1()");
}
Note that the "{}" braces is used as placeholders for variables, and they are served by order.
Logging Config File
A default logging configuration as available out of the box in the framework.
For having custom logging configuration, create src/main/resources/logback.xml
file which shall contain your custom configurations.
For unit testing, the logback file name should be named logback-test.xml. |
Resource Loading
Resources such as file and images loading at runtime looks easy, but its not. Where there are many scenarios. For example:
-
A file in the current working director could be read using
new File(fileName)
. -
A file in the in the class path (inside the jar or war or next to classes), could be read using the various
getResourceAsStream(fileName)
methods of theClass
,ContextClassLoader
, or the defaultClassLoader
. -
A file in inside a web applications,
context.getResourceAsStream(fileName)
.
In the framework, that is pretty easy, just call
InputStream in=JKResourceLoaderFactory.getResourceLoader().getResourceAsStream(name);
or even easier:
InputStream in=JKIOUtil.getInputStream(fileName)
Context API
Passing request and context information between different layers in applications could be needed in most applications. For example, context information could include:
-
Current User
-
Current System
-
Current Module
-
Current View
-
Current Action
-
Remote Machine/Port
-
App Machine/Port
-
Current Controller
-
Current session information
And others.
In J-Framework, the context information will be automatically populated on each request in JKDefaultFilter
, and
could be accessed using;
JKContextFactory.getCurrentContext().getXXX();
Context information are also shared across microservices using J-Framework-ServiceClient
project.
Version API
Version API is used to retrieve the appliaction components versions at runtime.
List<JKVersionInfo> versionsInfo = JKVersionManager.getInstance().getVersionsInfo();
for (JKVersionInfo versionInfo : versionsInfo) {
System.out.println(versionInfo);
}
Where JKVersionInfo
includes the following attributes for each jar the contains build.properties
:
-
Name : Name
-
Version: Version number
-
BuildTime: Building timestamp
-
RunTime: execution timestamp
-
DeployTime: Deployment timestamp.
The file format is discussed in [build-info] section
In web applications, you can show the build information using the version servlet at |