Thursday, May 28, 2026

Unable to login to Katalon Studio in certain enterprise environment conditions

 Another year, another post.

In this episode, my team member who was tasked with scripting up our regression tests in Katalon, had the opportunity to upgrade from v10 to v11.1.3 recently. We'd hopefully be able to take advantage of the newer features and explore any improvements it had to offer. The older version could still run with no issues though. We can also load up their websites just fine on our Chrome browser on that machine.

The installation was just a quick unpacking from their archive. But the built-in login could not connect, since the software had to call home to verify our license. The error came in the form of 

Unable to connect to https://testops.katalon.io.

Please verify your internet connection and Advanced settings, then try again. 

We had a bit of back and forth with their support, they guided us to witness a "fresh" installation, concluding supposedly with suspicions attributed to the ZScaler appliance that fronted our networks. 

We followed their instructions, adding the TLS certificates into the software keystore in our machine. We tried both the root and intermediate certs. It still didn't work. 

They suggested that we point the trust store to use Windows-ROOT instead. Another update involved adding -Djdk.tls.namedGroups for a series of ECC curves next. Their logs still reported a stack trace. It was for a SSLHandshakeException that noted to have "Received fatal alert: handshake_failure" as the complaint. 

 My team spent some time on their own to investigate. Full credits to my team member, a viable resolution was discovered, after feeding the configuration settings of the software to ChatGPT. The primary suspects flagged were these 3 properties observed in the .ini file:

-Djsse.enableSNIExtension=false

-Dsun.security.ssl.allowUnsafeRenegotiation=true

-Dsun.security.ssl.allowLegacyHelloMessages=true 

 The problem went away, and we were able to continue using Katalon Studio. We updated the finding to the support and the ticket was closed.

But wait there's more! Those lines were not found in the .ini file that came out of the box when their installer was unpacking. And neither did my team added those lines in. While I was doing a bit more investigation into this mystery, I executed the installer once more. The first line appeared again on its own. 

By some odd coincidence, it's possible that the framework or some library that their internals were using, had decided to re-append this flag on its own, for our installation. 

The property jsse.enableSNIExtension is part of the Java Secure Socket Extension "JSSE". It is used to toggle (turn off) the extension for Server Name Indication, which in our situation could be due to the client software that's part of Katalon Studio, deciding that our enterprise network was not modern enough, so it tries to compensate by turning off the capability... which inadvertently makes things worse off.

 When I tried installing the same package on my own machine however, the properties did not appear. So it's possible that there are specific conditions that the internals check on, only then will this extremely niche issue will crop up.

Monday, January 20, 2025

SeaweedFS complaining "Check data folder" when mounting volume (that obviously exists)

 I stumped myself twice on this problem. Here's hoping I won't encounter a third.

SeaweedFS requires a separate process to be executed for mounting each volume, such as the following:

 sudo ./weed volume -dir=”/data/seaweed/data1” -max=5 -mserver=”localhost:9333” -port=8080 &

There is actually a problem with the above command. Good for you if you're observant enough. I failed to notice it months after I first spotted it, when I had to revisit it.

SeaweedFS will return with "volume.go:149 Check Data Folder(-dir) Writable ”/data/seaweed/data1” : stat ”/data/seaweed/data1”: no such file or directory" from the above instruction. 

I found this issue, but it was not helpful. I tried to chmod the folder (again), this time to 777 even (this is just a dev env) to no avail. I revisited the exception thrown from Retracing my steps, I returned to their source code on github once more. That helped to jog my memory a little more. 

It was the auto-correct from my Outlook. I'd copied the command from my older email. Inclusive of the non-Linux quotation symbols. I resolved it and decided to email myself once more with the updated command this time round, to be sure.

sudo ./weed volume -dir=/data/seaweed/data1 -max=5 -mserver=localhost:9333 -port=8080 &

I figured, why make thing pain when thing simple work?

Friday, October 11, 2024

Adventures with CQN

Our project had some additions that needed to have minimal interference to the existing system. The underlying Oracle database would have had to grab out changes from one existing data storage "P" table to a new "S" interface table. The simple solution would have been to implement a database trigger on the INSERT/UPDATE/DELETE statements on table P, inserting new rows into table S.

However, our DBA had concerns from recent developments, that suggested we avoid this traditional approach. They had little to offer in terms of alternatives. And this is where our story begins.

Resulting from the assessment, I ventured forth to find alternatives.

We could implement changes to the code in our existing module, that could support our efforts. That is obviously not feasible knowing that we'd much prefer to not rock the boat that much.

I saw suggestions online discussing the use of interceptors. Obviously it'd involve code changes as well, but with the add negative of not being best practice since we'd need to then share the classloader for both the new and old modules.

Next, I wondered if it's possible to tap into JMX. One option explored the possibility of exposing Hibernate Statistics that way. Again, we'd need to make changes, however minor, to the existing codes. Moreover, the statistics do not seem to offer the behaviours we need for picking up table changes.

In the brainstorming session with our DBA, we'd also discussed about using the flashback archive, and even the Change Data Capture that is with our OGG setup. Both of which were dismissed for complexity.

We'd need a way out that our application development team can support on their own, with minimal intervention/support from our DBA team.

I thought I'd hit the jackpot with the DBMS_CHANGE_NOTIFICATION (synonym as DBMS_CQ_NOTIFICATION) package. I thought that I could implement the trigger asynchronously without leaving the comfort of the database tier. The online documentation and examples were sparse, but still made sense. But it couldn't work in our setup for some reason. I'd written up the stored procedure and everything, but it was next to impossible to troubleshoot. Returning to basics, I then prepared a basic EMPLOYEES table to follow the example to the letter. No dice. 

Following the Object Change Registration Example, I'd went on to GRANT the privileges, ALTER the job_queue_processes to non-zero, and even resorted to a simple UPDATE upon any of the events that were meant to trigger the procedure. None of it worked.

Giving up, our DBA suggested I seek help from Oracle. The ticket sat for 7 days after their first acknowledgement, as we waited for a response. I provided the test case using their example. And naturally, their response was that they could not reproduce my issue.

Meanwhile, I'd essentially moved on. The same CQN had a variation that involved writing Java code. My colleague shared me the basic example he concocted from online references. While the original sample code used ojdbc6, discussions pointed him to using ojdbc10. Oh dear. Our application servers only run on Java 8, meaning that we could only use ojdbc8. I tested using the "default" v12.2.0.1 of ojdbc8 (more on this later) by referencing the POM to online sources, and it broke. Oh no. Did this mean that this CQN approach was no go either? Well... I decided to put on my tinfoil hat and started searching desperately for answers. 

First off, we require DCN_CLIENT_INIT_CONNECTION to be enabled. This was to avoid involving traffic being initiated from the database to app. But the v12.2.0.1 of the ojdbc8 library did not come with this constant. Even passing the property in as a hardcoded String literal did not help. As I scoured the web, Google returned nary a Page 2 of results. And then I found it. The javadoc for ojdbc8 on this site showed that the constant existed. I checked through the jar files I found from mvnrepo.com and still could not see the value. Next, I dug into our dev servers to try my luck. In the ojdbc8.jar retrieved from our own database instance, was this one constant string. My conspiracy theory meter blew up. "This had to be an enterprise version thing", I thought. Satisfied, I proceeded with finishing up my work. A barebones servlet was prepared, without using Spring, without using Hibernate. The servlet served, the listener listened. 

I even had a sidequest for orphaned registrations. The PL/SQL block for deregistering does not work for notifications registered from an application. We needed to use unregisterDatabaseChangeNotification, that will also indicate the callback, in which we'd nuke all registrations queried from "SELECT * FROM USER_CHANGE_NOTIFICATION_REGS" in the database.

The dust settled, and I needed to work up our pipeline for my conspiracy. It was a hassle that I'd rather avoid, if we needed our team to manually install the special ojdbc8 jar for this. Delving into the "good" jar again, I noticed that the library indicated v19.24.0.0 in its manifest. Oh. That's odd. I did not realise that there had been many versions of ojdbc8 since v12.2.0.1 that were all available online. There was no conspiracy. The jar file I'd picked up internally was simply named ojdbc8.jar because Oracle would see no need to indicate version in their naming conventions in a product they supplied. I would not need to especially install the ojdbc8 jar afterall. All I needed, was to update my POM for the dependency to point to v19.24.0.0 instead of v12.2.0.1.

Along the way, there were some other notes that I'd picked up. 

In order to acquire an OracleConnection, it's not enough to simply cast the default ds.getConnection() object. Instead, it is a must to .unwrap for acquiring an OracleConnection, as well as to .unwrap to get the OracleStatement. Without the unwrap for the Staement object, our WSJdbcStatement would have failed a ClassCastException since it could not be translated into an OracleStatement.

My conditions involve all of INSERT/UPDATE/DELETE events. It is trivial to retrieve records based on the supplied rowid, which involves setting Oracle.Connection.DCN_NOTIFY_ROWIDS property to true. The challenge for this comes in the form recovering rows that have been deleted. Fortunately, the Oracle database comes with a feature known as Flashback. It's basically an undo button for the database. The more advanced capabilities include creating tables or entire databases, and also a DBMS_FLASHBACK package. Each of those involve more nuanced considerations for issues surrounding configuration such as clusters. What I was interested in, was just the use of the Flashback Query. It basically allows a regular SELECT statement to include an AS OF clause, with which, I could do SELECT * FROM MYTABLE AS OF TIMESTAMP SYSDATE - INTERVAL '2' MINUTE WHERE ROWID = ? in order to travel back in time and recover the data that was just deleted during the DELETE operation. Just don't forget to GRANT FLASHBACK ANY TABLE TO YOURACCOUNT before that. The caveat is that you probably should not flashback longer than necessary.

Had the CQN Listener not been successful, I was already exploring the use of ORA_ROWSCN while blindly querying the tables. I'd have had to snapshot the largest PK ID somewhere, to check for INSERTs in one table, and DELETEs in the secondary archival table.The UPDATEs would have become the blocker in this case. The System Change Number "SCN" would provide a pseudo PK ID that I can then snapshot similarly. The rabbit hole would likely lead me to the Flashback Query as well to use in conjunction with the ORA_ROWSCN pseudocolumn. Fortunately, the story ends here, as that sounds like a whole other can of worms to deal with.

UPDATE (21 Oct 2024): I did not expect a sequel but here we are. We had two environments to test in. A colleague suggested that I test in the other environment, just in case. And it worked. But we didn't understand why there was this discrepancy, so I highlighted this new observation to Oracle Support team. And thanks to them, we found out the cause of the discrepancy with regards to the DBMS_CHANGE_NOTIFICATION package. I was routed to the AQ team, who then requested for our health check report for that component. 

The example included a line for 

ALTER SYSTEM SET "JOB_QUEUE_PROCESSES"=4; 

The above is done, where the value simply needs to not be a zero. In order for the Change Notification to work "server side" within the database, Oracle employs a couple of modules internally. Namely Advanced Queuing, and the Job Scheduler, even though they aren't spelt out as dependencies in the documentation found online. 

Querying (as SYS no less)

SELECT * FROM ALL_QUEUES WHERE NAME like '%CQN%'; 

results in 2 rows returned for the CQN_EVENT_TABLE, whereas

SELECT * FROM ALL_QUEUE_TABLES WHERE QUEUE_TABLE like '%CQN%';

returns one row for the same. Naturally, I'd next query for

SELECT * FROM CQN_EVENT_TABLE;

This results in every single test message I'd ran successfully from the beginning of this exercise.

This meant that all the messages were being captured from the AQ but failed to follow up with the stored procedure. That was where the job scheduler comes in. I had initially followed the steps, where some documentation instructed to set =2, then others noted to set =4, I'd even set =10 when I noticed that we had a lot more processes going on from querying

SELECT * FROM ALL_SCHEDULER_JOBS;

This was why, returning to the health check report, the Oracle team having observed that job_queue_processes=0, which perplexed me. Unbeknownst to me, there was some history to the database that led to this setting, which effectively turned off the job scheduler. 

As well as, when I was updating the parameter to whatever value would not have made a difference, as I was merely making the change in the Pluggable DB "PDB" and not the Container DB "CDB" that was required for this to work. I had my DBA colleague help set the value to 80 (to align the value with the dev environment that did work).

I went further to revise my original PL/SQL script, just in case we still needed it. In which, I realised that I'd have needed the Flashback Query anyway.

And now we have both server and client side solutions available.

Tuesday, July 16, 2024

Use SeaweedFS with Apache jclouds

 Prior to this, there was very sparse documentation linking these two software. It might be common sense to some, but there was hardly any mention for setting up both to be used in tandem. So let's cut to the chase.

The main draw for Apache jclouds is support for S3 API in Java, across many platforms. The main concern for us in particular, was their BlobStore API.

Addition to pom.xml

<jclouds.version>2.6.0</jclouds.version>


<dependency>

        <groupId>org.apache.jclouds</groupId>

        <artifactId>jclouds-all</artifactId>

        <version>${jclouds.version}</version>

</dependency>

Code snippet

//Initialise connectivity 

BlobStoreContext context = ContextBuilder.newBuilder("s3")

    .credentials(identity, credential)

    .endpoint(weedMasterUrl)

        .buildView(BlobStoreContext.class);

// Access the BlobStore

BlobStore blobStore = context.getBlobStore(); 

ByteSource payload = ByteSource.wrap(payloadStr.getBytes("UTF-8"));

Blob blob = blobStore.blobBuilder(uuid)

    .payload(payload)

    .contentLength(payload.size())

    .build();


// Upload the Blob

blobStore.putBlob(containerName, blob);


// Don't forget to close the context when you're done!

context.close();


The above was practically lifted off the jclouds page. The specific point of attention would be the newBuilder("s3") that is used as a generic version of the "aws-s3" stated in their original sample.


"But SeaweedFS already has a large number of client libraries provided by the community!", you exclaimed. 

And you'd be correct. Yet they'd only be used specifically for SeaweedFS however. I'd neglected to elaborate earlier, that the S3 API offerd by jclouds is generically usable with any other (enterprise-grade) product besides SeaweedFS. By integrating the two, our development can adopt a lightweight alternative like SeaweedFS, while the main production deployment takes on a heftier software, all while using the same library, which is offered by Apache no less.


This was still largely unexplored territory for some of us, so setting up SeaweedFS was more nuanced than we expected. 

This is what it took:

  1. Start the master server: sudo ./weed master &
  2. Start a volume server: sudo ./weed volume -dir=”/data/seaweed/data1” -max=5 -mserver=”localhost:9333” -port=8080 &
  3. Start filer and S3 service: sudo ./weed filer -s3 &
There was a gotcha in there that I had to figure out on my own. The Getting Started page only mentioned starting the master and volume. You could even interact with the service using cURL once it's started. But the Java codes still couldn't talk to seaweed.

The wiki even had a section describing how to use s3cmd to communicate with seaweed. I just didn't get it. Until I found this article that vaguely mentioning that the server needed to be started up specifically with s3 as an option.

I had to return to the ./weed --help to get more clues. On hindsight, none of this would have been an issue had I ran the server option instead of running the master and volume processes separately. But I felt it necessary to adopt a structure we might expect eventually. 

And with the filer -s3 started, the s3cmd could be made at last. I reckon I'd have been still scratching my head, had I not adopted s3cmd for verifying the setup outside of the codes. I could make my bucket and be on my way at last (because I think that our actual codes probably shouldn't be creating buckets on its own that easily).

Friday, September 30, 2022

Kubernetes Dashboard Secret

The short gist

 Running the Windows Command Prompt, load the local proxy:

kubectl proxy

Then execute cURL like so:

curl -H "Content-Type: application/json" -d "{}" http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/serviceaccounts/admin-user/token

The TokenResponse JSON object should appear in the reply that ends with

"status": {

"token": "eyJhb...",

"expirationTimestamp": "2022-09-30T10:01:13Z"

}

The long story

There used to be a time when the Kubernetes Dashboard came out of the box with a command for retrieving the default token using the command line. Until it didn't.

Scouring many forums and discussion threads, it seems to have been removed.

Secret token not generating - General Discussions - Discuss Kubernetes

Not able to login to Kubernetes dashboard using token with service account - Stack Overflow

Even the official documentation was nary a help.

dashboard/creating-sample-user.md at master · kubernetes/dashboard · GitHub

dashboard/README.md at master · kubernetes/dashboard · GitHub

Some people suggested overriding with the --enable-skip-login flag, which didn't work for me either.

Running Kubernetes and the dashboard with Docker Desktop (andrewlock.net)

The trail eventually lead me to the suggestion of calling the K8s API to get what I want.

TokenRequest | Kubernetes

Which in turn allowed me to locate this post that gave some useful headway.

A Look at How to Use TokenRequest Api | jpweber blog

This has troubled me for over a month as I had to repeatedly get interrupted with other tasks. I'm at least satisfied that this has arrived at a conclusion before the weekend.

Friday, January 28, 2022

Carving the Rosetta into an ink stone

Turning what was into what will be

If technology ages like people, then according to this, the system we worked on was old enough in human years to vote. The enterprise web application had served the team of users for a good many years, with a long-deserved refresh. Care and time was taken to assess the changes needed. This was all the more important given that there was practically zero code freeze period available, as the old system had to roll out new changes very close to the go-live of the new version. Things tend to be hectic when similar tasks have to run in parallel. Here were some of the key considerations that we had planning everything out. 

Replacing an important desktop feature

One of the major technology that had to be benched, were the Java applets. The industry had gradually shifted away from its use over the years, but it remained typical of intranet applications to continue in its use due to a variety of factors. 

In order to throw applets out the door, one substitute was the use of local services. Many consumer product manufacturers had been providing these desktop software already. These were small programs sitting in the system tray, that are responsible for minute tasks on the client operating system. 

An application running from a web browser can only do so much, when interacting with the user desktop. Installing such a software caters for flexibility that typical web apps cannot afford, like initiating the printer spool to the hardware device without launching a separate window. We had an two implementations, using REST-based asynchronous calls for basic functionality, and more tightly coupled integrations that required WebSockets for payment terminal communications. Separating the modules this way helps delineate separation of tasks. 

Certain pitfalls we encountered, were surrounding the XSS and CSRF protections. We had to factor in the SSL certificate as part of the package as well, ultimately culminating in an installer which was able to bundle a JRE of our choice. 

API-ing the monolith

Ye olde web app was built as a monolith. Like a good coming-of-age story, this concept also required a paradigm shift. Due to the many limitations, it was impossible for the upgrade to be completely rewired into microservices. It was also impractical for this application to be totally disassembled for our many use cases. I decided we should take the middle ground, and went with a hybrid approach; it was not quite a monolith, but not yet a microservice architecture. 

An authentication service was first built from the ground up. This was a module that could run on its own, within our traditional application server environment. By ensuring that it was mostly self-contained, there is an element of future-proofing by requiring APIs to be accessed via REST-based calls. I use "REST-based", because our environment discourages fully RESTful APIs due to security concerns. This was as close to a zero-trust API flow as I could build in our case. The use case for this service was mainly for M2M (machine-to-machine) communications. Products offering OAuth solutions in the wild typically expect three-point flows, meaning that it was not immediately available for an off-the-shelf framework that provides the M2M flow which I was looking for. 

Following this construction pattern, we'd also built another module for exposing APIs to external interfaces. More of the lean REST-based request/response pairs are applied here, to avoid encumbering the consumer with legacy fields. This API service was a companion to the authentication service, as the caller is forced to provide a token with each query. A token which can only be requested from the authentication service. This API service will then make an internal API call to the authentication service for validating the provided token. 

Speaking of internal APIs, we had further expanded on that in the existing monolith. There were further offsite modules that had data transmission requirements with the central application. Applets were used to address this, but without it, the gap had to be fulfilled by the more readily usable REST-based options instead. Endpoints were added to replace what used to be Java RMI objects. 

Modernising the team processes

The suite of development tools used to be CVS with Mantis. The new direction was with Gitlab and Jira. Getting the team onboard certainly took getting used to. The development workflow had to be adjusted as we got used to the different behaviour of Git, where the source code was committed locally first, and had to be "pushed" to the remote repository subsequently. 

The old issues in Mantis were boxed and shelved entirely, as we embraced a new leaf with Jira. Certain flexibilities we were used to in Mantis were no longer available with Jira. The team had to be regularly reminded to ensure that issue status and resolutions were kept updated. The web based interface definitely helped in maintaining oversight of the outstanding issues, with priorities and progress.

Code review remained manual. This was a necessary evil, due to the immense code base, containing a multitude of legacy codes, intermingled with business logic and potentially outdated or deprecated codes. It was pertinent that all new changes were annotated clearly with start/end comment blocks. The best practices I had for software development had to be reinforced and reminded regularly to the team.

The eventual go-live of the new system was no mean feat. Part of the effort was shaved considerably and efficiently. A colleague had taken up the heavy task of preparing a installer for the event. Packaging an installer helped to streamline the deployment process, as we had various software that had to be installed into more than 100 workstations on the network. It didn't help that the desktop engineers were operated by a different vendor. We were able to take advantage of the PowerShell script, further enabled by the PSADT framework. The ten plus different installers we needed to run, could then be consolidated into this single installation. It was not perfect, and still as yet can be improved further. But it is another step in the direction to be taken.

Getting here

Happy new year! Each step that was taken, had its own story. As we build into the future, we should always keep sight of what is on the horizon. In spite of the limitations of our time, certain concessions can be made to ensure that progress in made in the correct direction. Baby steps are better than no progress at all. 

A shout out and major thanks to the team that I'd worked with. Our work is just getting started on the new version of the system, but kudos to everybody for getting this far!

Monday, November 15, 2021

Specifying the MQ username for connecting to a remote queue

Long before your time, there was a WebSphere 7 function, for using JMS to talk to Message Queues. The connectivity made use of (we believe) a default "mqm" username for accessing messages. Both the application server and codes have no awareness of such an arrangement. Nobody had any idea about this, until we were forced to dig deep into this discrepancy.

Then along came WebSphere 9, which turns out was slightly more advanced. The default is no longer used. Instead, it uses the service account that the application server was running on. This introduced some problems. The same identity needs to exist on the remote queue server that the WAS9 is connecting to. 

For most people, this might have easily concluded by having the same account be created on the other side. Naturally, this was not what is happening to warrant this post. In light of some revelations, a username that is different from the one already running the WAS9 was to be used.

I found out that it was risky to try setting the clientID, with due consideration for resource contention in a clustered environment. We tried poking around the J2C authentication alias, but it seemed to require a lot more configuration. The other option, was the username, as suggested by IBM documentation.

In order to set the value in, I located this UserCredentialsConnectionFactoryAdapter for Spring. As suggested here as well, 

For example, when using Basic client authentication, the username and password set for the Initial Context and used for the JNDI connection are inherited from the JMS data connection. However, these properties can be overridden by a username and password provided in the Connection Factory.

 I'd followed the example approximately:

 <bean id="myTargetConnectionFactory" class="org.springframework.jndi.JndiObjectFactoryBean">
   <property name="jndiName" value="java:comp/env/jms/mycf"/>
 </bean>

 <bean id="myConnectionFactory" class="org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter">
   <property name="targetConnectionFactory" ref="myTargetConnectionFactory"/>
   <property name="username" value="myusername"/>
   <property name="password" value="mypassword"/>
 </bean>

We fiddled a bit with it and took it for a spin. It seemed to take, so we were quite relieved that we didn't have to spend another 2 weeks rewriting a bunch of codes for a fix. At this point, we can confirm that the JMSXUserID remains as "mqm", according to our debug logs.

This had been a very obscure lesson, with nary an article that detailed what needed to be done. Our work is yet to done (will it ever?) but this was quite the wild ride that my colleagues and I took.

Thursday, September 30, 2021

Configuring JVM for Tomcat Service Manager

It's been slightly more than a year since my last post, but my team had recently ventured into preparing our own deployment script. At the behest of an eager member, we'd built up a PowerShell script using PowerShell App Deployment Toolkit, also known as "PSAppDeployToolkit", or even shorter as PSADT. This was a script meant to automate installation of a number of programs on the target Windows machines, silently and automatically. 

With the background of what we were doing established, this was about one specific setup we wanted to configure. One of the platforms we were using, was Tomcat 9. Fine, it was TomEE 9, using Tomcat 9, but you get the idea. Tomcat was to be installed as a service by the having our script chained to trigger the standard "service.install.as.admin.bat" script. 

Then we realised that the JVM would default to "auto", which means using whichever JRE it can find. We prefer to stick to our version for consistency. The easiest was for us to edit the provided batch script. After finding this page of documentation, I started poking around the script more. We need the --Jvm="/path/to/the/jvm.dll" option added in. 

The standard configuration out of the box is this:

"%EXECUTABLE%" //IS//%SERVICE_NAME% ^
--DisplayName=%SERVICE_NAME% ^
--StartClass org.apache.catalina.startup.Bootstrap ^
--StopClass org.apache.catalina.startup.Bootstrap ^
--StartParams start ^
--StopParams stop ^
--Startup auto ^
--JvmMs=512 ^
--JvmMx=1024 ^
--JvmSs=2048 ^
--StartMode jvm ^
--StopMode jvm ^
--LogLevel Info ^
--LogPrefix TomEE

So I just need to add it in, right? I tested.

"%EXECUTABLE%" //IS//%SERVICE_NAME% ^
--DisplayName=%SERVICE_NAME% ^
--StartClass org.apache.catalina.startup.Bootstrap ^
--StopClass org.apache.catalina.startup.Bootstrap ^
--StartParams start ^
--StopParams stop ^
--Startup auto ^
--Jvm=%PR_JVM% ^
--JvmMs=512 ^
--JvmMx=1024 ^
--JvmSs=2048 ^
--StartMode jvm ^
--StopMode jvm ^
--LogLevel Info ^
--LogPrefix TomEE

It seemed fine on the surface. And after more elaborate tests, I realised that doing so would break the Tomcat Service Manager. The memory values (JvmMs/JvmMx/JvmSs) would not take. Neither did the StartMode and StopMode parameters. I couldn't be certain if it was because of how the PSADT script worked. I'd only extracted the portion of our whole script for Tomcat to test on after all. After some more fumbling, I thought I could try adding it to the end, and gave it a shot. The command wouldn't recognise it past the LogPrefix parameter at that point. It was then that I decided to move it up 2 lines.

"%EXECUTABLE%" //IS//%SERVICE_NAME% ^
--DisplayName=%SERVICE_NAME% ^
--StartClass org.apache.catalina.startup.Bootstrap ^
--StopClass org.apache.catalina.startup.Bootstrap ^
--StartParams start ^
--StopParams stop ^
--Startup auto ^
--JvmMs=512 ^
--JvmMx=1024 ^
--JvmSs=2048 ^
--StartMode jvm ^
--StopMode jvm ^
--Jvm=%PR_JVM% ^
--LogLevel Info ^
--LogPrefix TomEE

Ha! It finally worked. The JVM was no longer "auto", the memory values took effect, and the start/stop modes were in as well. There was very little details surrounding this specific configuration, much less documentation relating to this particular quirk of positioning the option correctly.

Thursday, September 3, 2020

My minimum standards for coding in Java

People have a tendency to overlook certain issues, and may require gentle reminders every so often, to nudge them in the right direction. I'd imagine a simplified list would help with rehearsing the drill. This happens everywhere, including at work with my team, with regards to Java coding for webapp development. Here's what I've come up with succinctly abbreviated as "SCROLL":
  • Switches
  • Comments
  • Reusability
  • OWASP
  • LogLevels

Switches

Implement soft toggles that allow the running application to toggle features without server restarts. This is particularly useful for rolling out enhancements prior to the actual go-live date.

One way is to store such values in the database via system variables or code tables. The "enhanced" code should then check the switch each time it is called. Of course, not all situations can adopt this method, but I'd think that doing this as much as possible will be of great help.

Comments

Elaborate explanations in the codes will help others understand your thought processes in future. I find it equally helpful for when I revisit very old codes that was written by yours truly. The explanations for changes in workflows will be useful for troubleshooting several years down the road. 

A recommended format would be //name, date, description for single-liners. The next person could potentially approach the person who built it, and be able to discern a timeline of which set of codes came after which.

An added bonus would be if the comments were following conventional Java /** **/ format such that it can show up properly in generated javadocs.

Reusability

Optimised codes can be refactored into methods that can be write-once-run-anywhere (at the code level). This includes system variables and constants. Also part of this category are Util classes that serve a common, generic purpose.

OWASP

Security should never be an afterthought, where the OWASP still is the recommended set of guidelines that web developers should work with. Proper validations should be ensured (even for basic "!= null" checks) which will aid in the long run in case of code scans and security tests.

LogLevels

Logging is important, but so is the correct use of loglevels. Only use INFO level for production environments, while aiming to only output a single line containing all the useful information without generating extraneous logs. A sub-point would be to avoid logging sensitive data, and sanitising the output if it's necessary.


The above are meant for highlighting specific areas to focus on, in the name of brevity. Do you have any others you'd consider adding/replacing to the list?

Wednesday, August 19, 2020

StackOverflowError during Maven assembly

While trying to rebuild a project after reviewing changes from a team member, the compilation threw a Stack Overflow error (not the website) in the process of assembling the final JAR file.

Exception in thread "main" java.lang.StackOverflowError
    at sun.nio.cs.SingleByte.withResult(SingleByte.java:44)
    at sun.nio.cs.SingleByte.access$000(SingleByte.java:38)
    at sun.nio.cs.SingleByte$Encoder.encodeArrayLoop(SingleByte.java:187)
    at sun.nio.cs.SingleByte$Encoder.encodeLoop(SingleByte.java:219)
    at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
    at java.io.PrintStream.write(PrintStream.java:526)
    at java.io.PrintStream.print(PrintStream.java:669)
    at java.io.PrintStream.println(PrintStream.java:806)
    at org.slf4j.impl.SimpleLogger.write(SimpleLogger.java:381)
    at org.slf4j.impl.SimpleLogger.log(SimpleLogger.java:376)
    at org.slf4j.impl.SimpleLogger.info(SimpleLogger.java:538)
    at org.apache.maven.cli.logging.Slf4jLogger.info(Slf4jLogger.java:59)
    at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:464)
    at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:467)
    at org.codehaus.plexus.archiver.AbstractArchiver$1.hasNext(AbstractArchiver.java:467)

Some suggestions included looking at the JRE memory heap. Another hinted at the thread stack size instead. Turns out that the latter was more correct. Naturally, setting the MAVEN_OPTS value in the System PATH variable did not help. Restarting Eclipse didn't help either. 

To which, my next line of thought went towards wondering, what if the value was set into the JRE when executing the Maven build. 


Here's what I did:

  1. Navigate to Eclipse
  2. Run Configurations > [Select build profile]
  3. JRE tab > VM arguments
  4. Input "-Xss2m"
  5. Apply and Run
The thread stack size would have been increased to 2MB at this point for the build, the complain goes away, and the build completes successfully (for me at least).

Thursday, July 2, 2020

Death to Java Applets

Preface
Why are we still on this topic in the second half of the year 2020? Unfortunately, not all enterprise applications can cut off such dependencies as easily as your next-door neighbour hosting their WordPress e-commerce shop in the cloud.

Intro
In the heydays of applets, the UI components and integration with webpages proved to be highly sought after. Certain operations and functionalities were useful wayback when, which includes but not limited to the following:
  1. Remote Method Invocation;
  2. Native Library Interfaces;
  3. Function calls on the local filesystem;
While the world is preparing to mourn the passing of the Adobe Flash Player in a few months from now, the other veteran from the same era had received less attention, partly due to its application in less consumer-centric purposes.

In this day and age, what could possibly replace such a utilitarian platform for enterprises that employ web-based applications, built around access via browsers? While it may be apparent to some, it might not be equally obvious to others. Let's break it down some.

Ajax and JSON
The basic transport employed by web apps has to be HTTP(S) these days. The use of jQuery, amongst many other libraries have been staples for some time. Some of the functionalities for Java RMI can effectively be subsumed by asynchronously transmitting JSON traffic to be dealt with on the serverside. There is less dependency on use of Java objects, and this would prove more useful in a heterogeneous application ecosystem. Testing is simpler via SoapUI or Postman, and there are standard HTTP client libraries available in your favourite language.

Local Services
While Cross Origin Resource Sharing (CORS) is quite a mouthful, it's rather trivial to allow access control in your codes. Yes, what I'm proposing is for hosting a lightweight server for responding to requests local to the workstation. Of course, security has to be enforced, to prevent remote abuse. Once the various concerns have been addressed, a simple tray program could make for a powerful utility for supplementing your web application. This service essentially serves as a local IPC conduit between the browser and native functions, using HTTP as the socket transport, and JSON as the message format.

Web Sockets
The standard REST APIs typically employ asynchronous transmissions using GET/POST methods, and should suffice if your operations are straightforward enough. The only time you might want to consider taking out the big guns, are if you have requirements for bidirectional communication with a native device. This is where Web Sockets will prove useful. That said, it is less trivial to implement SockJS or STOMP on your mini service.

Conclusion
Taking all the above into consideration, I'd worked on a side project that was effectively a Local Service program. It would only respond to requests from localhost, and was capable of loading pluggable handler classes at runtime.  These plugins each serves a different purpose, all running off a different context path. No Web Sockets for now. Operations that could potentially be offloaded to server-side, would be done that way, but we still had to deal with certain tasks local to the user workstation. This was our way out.

Let me know if you'd found a different way out.

The Java Applet is dead. Long live the Java Applet.

Thursday, January 23, 2020

Poor Image Quality in Generated PDF

Happy New Year!

We'd recently had the opportunity to upgrade some codes from codes related to PDF generation. The source was previously coded to manually position elements (e.g. 100px from the left, 20px from the top) for the output. The revised strategy was to adopt a Word Document as the template, with placeholders prepared. An XML will then bind to the template at runtime. But this is trivial only for text content. The images were slightly more complicated, but we were able to overcome it, with slight adjustments to Docx4J.

The problem arose however, when we noticed that an image with fine detail would then appear to look very poor in quality when viewed on the PDF. Yet, if the image were to be copied from the PDF, and pasted into a separate image viewer, it looks clean and crisp.

The couple of assessments I had initially led to deadends:
  1. It was not due to AffineTransform having lousy output;
  2. It was not due to the process of converting the DOCX to PDF format;
  3. It did not make a difference regardless of the image format provided (PNG/JPEG/BMP);
  4. It was not due to the difference of colour spaces (e.g. BufferedImage.TYPE_INT_ARGB);
  5. It did not make a difference setting the DPI into the PNG metadata;

After a bit of investigation, by stepping into the Docx4J classes at runtime, I started noticing that the DPI use was suspect. Diving into the library sources, I noticed that the preprocessing stage for generating the PDF will attempt to determine the dimensions of each image. From there, I was able to surmise that I could further change the codes on my end.

//create the image part
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, imageBytes);
//derive inline element
Inline inline = imagePart.createImageInline( null, "image alt", 0, 1, true);
//retrieve dimensions to fix
CTPositiveSize2D ext = inline.getExtent();
ext.setCx((long) (ext.getCx()*0.75));
ext.setCy((long) (ext.getCy()*0.75));
inline.setExtent(ext);

After adding the fix (in bold), the image appeared much cleaner. When copied into an external image editor, the image looked much closer to scale in comparison to it's counterpart viewed from the PDF viewer at 100% zoom. The image dimensions (in pixels) would then of course have to be adjusted larger to compensate.

As an aside, I'd also learnt about 2 new units of measurement:
  1. "mpt" - millipoints
  2. "twip" - twentieth of a point 
 Not that they are useful in any way outside of this situation.

Friday, November 15, 2019

Parent ClassLoader Last on WebLogic

There were some requirements recently to convert some source files from WebSphere to WebLogic as the deployment target. I'm not particuarly familiar with WebLogic as the projects I've been involved in, largely employs WebSphere. Some hijinks ensued and I'd reached a familiar problem regarding NoClassDefFoundError.

My first lead pointed me to this question, where a helpful answer gave some insight to how WebLogic works. The solution was to add a <prefer-web-inf-classes> XML configuration. My assessment at this stage was that this issue seems similar to how WebSphere deals with the ClassLoader problem, where in certain cases, priority needs to be given to files found in the application, due to identically named classes.

Option to set the ClassLoader policy from within WebSphere Admin Console

I proceeded to try and find out if there was a configuration option to set this from within the WebLogic admin console. There was not. A second link seemed to corroborate this finding. And then I found the official documentation. The only was to set this up was via an XML entry from the weblogic.xml file as part of the deployed application:
<weblogic-web-app>
  <container-descriptor>
    <prefer-web-inf-classes>true</prefer-web-inf-classes>
  </container-descriptor>
</weblogic-web-app>
This wasn't a convenient fix, but it was a solution after all. Albeit it certainly would have been helpful, had this been made available from the admin console.

Monday, March 4, 2019

Axis 1.4 support for TLSv1.2

The library doesn't understand that out-of-the-box. It will always perform a handshake using TLSv1 instead. This happens even after the initial handshake was done in TLSv1.2 in the rest of the program. adding -Djavax.net.debug=ssl:handshake:verbose reveals that the ClientHello would always successfully be established on v1.2 but the next call would be another ClientHello in v1 afterwards. Neither of -Dhttps-protocols=TLSv1.2 nor -Djdk.tls.client.protocols=TLSv1.2 helped. This helped with getting the debugging to this stage.

The SSL plugin found on Github was one possibility, but I was hoping to find a solution which is more lightweight. Initially, I found this, which hinted at configuring the AxisProperties. The rabbit hole lead me to the suggestion of customising the SecureSocketFactory next. Digging deeper, I finally found the setting that "unlocked" TLSv1.2 for Axis. It was the setEnabledProtocols that mostly did the trick, and allowed me to get a move on.

With the customised SocketFactory, setEnabledProtocols, I could finally run the program as such

<JAVA_HOME>/bin/java -Dhttps.protocols="TLSv1.2" -Djava.security.properties=java.security -jar MyApp.jar

The java.security file was merely a text file containing 2 empty property assignments:
ssl.SocketFactory.provider=
ssl.ServerSocketFactory.provider=

It certainly helps starting off the week in a good way.

Friday, March 1, 2019

Rediscovering outdated quirks in IE11

Welcome to 2019, it had been exactly 2 months since the year started, and finally I'd came across notable oddities.

As detailed in the post here, during the course of my work, it was discovered that while the browser was already Internet Explorer 11 running on Windows 10, the production environment had maintained a compatibility mode view for the legacy application.

Testing the same setup on my own machine turned up interesting bugs before the above was realised. The getElementById was retrieving by the "name" attribute on HTML elements successfully in IE7, but because my own IE11 was not configured this way for the site, the errors started appearing. We'll endeavour to work towards fixing all similar bugs in the application, but for now, leaving it on compatibility view would have to do.

Wednesday, September 5, 2018

Mysterious Latency

The project was underway and we've started looking into performance tuning. The eG system monitoring tool flagged out webservice transactions lasting more than a few seconds. The application was apparently choking for more than 4 seconds on Java code. But it was not just any chunk of logic. The method being flagged was org.apache.cxf.transport.servlet.AbstractHTTPServlet; which was part of the CXF library. This was not even part of our custom codes.

I'd fired up the Java VisualVM to inspect the thread stack on my local machine. We had a couple of guys help take a look at the latency as well, diving into the log files. We'd pretty much stumbled upon the same issue from different angles.

The framework we're using adopts CXF, which in turn is able to output SOAP requests and responses to the log files. By setting the loggingInInterceptor and/or loggingOutInterceptor, we were debugging the XML content in the staging environment. The "limit" property was set to "-1" for both, in order to log entire payloads. This, of course, did not sit well with the other environment looking at the performance aspects of the system. This logging activity was costing an additional 5 seconds at times, according to the log (ironic, I know). We'd then recommended for the property to be set to "0" instead, so that zero bytes of the payloads will be logged.

Connecting Java VisualVM to WebSphere 8
Referring to this post, I'd added the following JVM arguments into my WebSphere
-Djavax.management.builder.initial= -Dcom.sun.management.jmxremote  -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=1099 -Djava.rmi.server.hostname=MYHOSTNAME

Subsequently following this article that's a bit clearer, I'd then connect via VisualVM to the localhost:1099, which was then able to sample the method calls with indications of self execution times.

Configuring CXF logging
The XML configuration of the CXF interceptors look something like this:
<bean id="loggingInInterceptor" class="org.apache.cxf.interceptor.LoggingInInterceptor" parent="abstractLoggingInterceptor">
    <property name="limit" value="-1"/>
</bean>
<bean id="loggingOutInterceptor" class="org.apache.cxf.interceptor.LoggingOutInterceptor" parent="abstractLoggingInterceptor">
    <property name="limit" value="-1"/>
</bean>

Which goes without saying, that the limits should be set to a value other than "-1" if you don't want/need the SOAP for debugging, especially outside of a development environment.

This is extra important if your XML includes one or more image binaries encoded as Base64 strings.

Wednesday, June 6, 2018

Maven Install does not Package


This episode started with our use of Jenkins. But ultimately it had to do with how Maven was behaving with regards to the various lifecycle phases. Our original command was "mvn clean install". 

This would be the norm for most of our projects, except for one module which was generating stub files from the WSDL of another. One particular object - let's just call it com.blogger.quirksofit.vehicles.QuirkyParentType - is being extended by numerous others in a com.blogger.quirksofit.vehicles.cars subpackage. Class QuirkyParentType will always be generated into com.blogger.quirksofit.vehicles, but depending on how the project JAR was being built, QuirkyParentType may or may not be generated into com.blogger.quirksofit.vehicles.cars where the subclasses reside.

This problem doesn't surface until our tests where the first module calls the other. The backend replies with an ambiguous XML tag for QuirkyParentType which the front determines to be part of the com.blogger.quirksofit.vehicles.cars subpackage. And if the Maven build was not done correctly, this will all break.

We noticed that "mvn clean install package" helped, to a certain extent. The source file for QuirkyParentType was generated, but it was still not propagated into the Maven repository. What did manage to do the trick, was when we tried to call "mvn clean package install" out of exasperation. We'd previously assumed that the Install phase should call Package first. This did not happen for some reason. And by putting package after install, the repository was updated before the source was generated.

Finally, we put all of this into Jenkins to complete the build and left for home, and our late night dinners.

Quartz Scheduler needs synchronised clocks in clustered environment

We had batch jobs that we refusing to work and were misbehaving with erratic schedules that were rarely successful. The tasks were making use of Quartz which were sitting in a cluster of servers. We didn't think much of it that one of the servers were misconfigured with a different timezone previously as this was in a development setup. This errant server was 8 hours behind, using UTC as its timezone.

A few attempts later, I managed to convince the machine to adopt our timezone instead. But the application server still refuses to follow suit. Fortunately, I found out how to change its timezone as well, after a few false starts.

The jobs then came back to life after they snapped out of their jetlag. The difference in timezone was causing a lot of misfirings to occur as each cluster node checked in on each other via the database.

Tuesday, May 15, 2018

ConcurrentHashMap bug compiling on Java 8 for Java 7

It's been a long couple of months, but I'm back with another quirky discovery.

TL;DR - we changed all declarations of ConcurrentHashMap to Map.

The current project has a number of components. Some are to be deployed on a Java 7 runtime, others make use of newer capabilities on a Java 8 runtime. While trying to streamline the build process using Jenkins, this issue came up while I was switching over from Java 7 to 8. We'd normally assume that it'd suffice for Maven to build with the specific major version in mind using maven.compiler.source and maven.compiler.target configured either in the POM or over the command line. Despite these additions, the runtime would still complain of the following error somewhere in our codes:

java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;

A bit of searching turned up this post that explained

Notably the Java 1.7 ConcurrentHashMap#keySet() returns a Set<K> while the 1.8 ConcurrentHashMap#keySet() returns a ConcurrentHashMap.KeySetView<K,V>.

The OP then proceeded to suggested to simply edit the declaration of the ConcurrentHashMap to Map as a workaround.

Other solutions I found mentioned
  1. configuring the -bootclasspath of the Maven build path to point to the rt.jar of the older JDK;
  2. setting up a separate Jenkins/Maven for both halves of the project targetting different Java versions;
Both solutions are not feasible essentially because besides the fact that I'm unwilling to break up the Jenkins for the components using Java 7 and 8 respectively, such a differentiation would eventually get lost in translation, and anybody on the team that tries building similarly on their own (using any combination of JDK/Maven/IDE) may still encounter the same problem unwittingly, thereby costing even more man-hours to investigate and fix.

Editing the declaration would still seem less painful in the long run.

Digging further turned up a bug report which was closed as "Not an issue". The workaround suggested was not exactly helpful, compared to the first article I'd located, but apparently it was related to JSR166.

Saturday, December 23, 2017

XMLHttpRequest fails on Internet Explorer 11 with "Access is denied"

Steps to reproduce:
  1. Open Internet Explorer 11
  2. Access "google.com"
  3. Browser should redirect to "https://www.google.com"
  4. Load Developer Console
  5. Enter this line into the console
    1. new XMLHttpRequest().open("GET", "http://www.google.com", true)
  6. Console returns "Access is denied."
  7. Enter this line into the console
    1. new XMLHttpRequest().open("GET", "https://www.google.com", true)
  8. Console returns "undefined"
When I first encountered the problem, what I found from here was this

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost/', true); // This line will trigger an error
xhr.send();
 
What I didn't notice (until much later, now) on the very same page was this
In addition to the trusted site requirement I found that the problem was not fixed until I used the same protocol for the request as my origin, e.g. my test site was hosted on a https but failed with any destination using http (without the s).
This only applies to IE, Chrome just politely logs a warning in the debug console and doesn't fail.

Which led me to finding this which mentioned
Requests must be targeted to the same scheme as the hosting page
This restriction means that if your AJAX page is at http://example.com, then your target URL must also begin with HTTP. Similarly, if your AJAX page is at https://example.com, then your target URL must also begin with HTTPS.

This was a pain in my butt for the past few weeks now. It didn't have anything to do with the hardened workstation, whitelisting of URLs, or firewall configuration. The "Acces is denied" could have been a little bit more helpful with clues. It wouldn't show up in development, until you start deploying your codes into staging or production environments that stuff like SSL starts getting in the way with this kind of issues.

I'd been swamped with working on something massive for the past half a year. It hasn't been easy at all with the long drawn marathon of development work that I've been involved in. But I still think that computers are easier to understand than humans.