State of my Workspace 2021

Jan 15, 2021, 10:59 AM

Tags: sotu
  1. State of my Workspace 2017
  2. State of my Workspace 2021

A few years back, I wrote a post about the State of my Workspace, and I figure now is as good a time as any to write an updated version.

That said, looking through my old post, it's a little surprising how much remains the same, considering how many of the categories where allegedly in transition at the time.

Eclipse is Eternal, Apparently

Over the years, I've spent varying amounts of time in IntelliJ, and it's remarkably good in a lot of ways. I love how well-integrated its features are, and the revamped Endpoints pane in the latest releases is outstanding. It's speedy, consistently-updated, and well-considered. It particularly shines when working with a single app (either an individual thing or a small multi-module project), such as working on this blog.

That said, I still use Eclipse daily and always return to it when I try to switch away. Eclipse itself has been improving steadily too. Recent quarterly releases have focused a lot on performance, and it's paid off - the switch to non-blocking completion proposals in particular has brought some of the editing snappiness that other editors revel in. In addition, the Wild Web Developer project, though still jankier than, say, VS Code, does a good-enough job bringing fully-modern JS editing to Eclipse by way of using the same underlying LSP as other editors (the fact that it's also the backbone of NSF ODP's Eclipse support helps too). It also has some of the inline Java hints like IntelliJ does now, too, dubbed "Code Minings".

There are a few things that keep me coming back to Eclipse other than just "it's speedy like others now", too. Most of my work involves working with multiple sprawling Maven trees from distinct repositories. While IntelliJ can do that by way of importing modules, Eclipse's UI just makes it easier to manage. There's also the eternal back-and-forth about Build Automatically, and I come down thoroughly on Eclipse's side there. While IntelliJ has some options to do something like that, it's not as consistent as Eclipse. With Eclipse, I can be confident that the Problems pane will always show everything applicable. In general, I just feel more in-control of a large set of projects when working in Eclipse, and that goes a long way.

Issue Trackers Are Even Worse Off Now

Keeping track of open issues for my various tasks - both open-source projects and client work - remains a real thorn in my side, and the situation has degraded further over the years. I quite liked using the app Ship years ago, but then the company behind it shut down. The Mylyn Bitbucket connector, too, kept breaking and I gave up on it entirely a while ago. This has left me back in the situation of manually checking various browser tabs to follow everything, and that stinks.

Every once in a while, I've tinkered with writing my own little coordinated issue-tracker app to bridge all the differences, but it never really gets off the ground. It's just one of those things where it never really makes sense to sink a bunch of non-billable time into slightly improving the way I manage actually-billable time. Maybe one day it'll tip over the mental threshold and I'll actually solve the problem. We'll see.

My Computer Is Due For A Change

I ended up buying the iMac Pro I mentioned in my last post, and it's been pretty decent. However, I've hit the same trouble that Marco Arment of ATP ran into, which is that the fans on this thing go constantly. It crept up over time, with them just spinning up when I would e.g. compile a bunch of stuff at once, but now they're going basically any time I use it. It's still under AppleCare, but, between the pandemic and the fact that there's no time when it's convenient for me to be without my development environment for a week, I haven't taken it in. It's just led to be getting more and more annoyed over time.

Fortunately, the M1 Macs should be an opportunity to cut the Gordian knot: my old MacBook was due for a replacement, so I'm swapping that out for an Air. In theory, that Air will be equivalent or better than the iMac Pro for the type of work I do anyway, and I'd long ago moved my Windows VM to a PC in the basement. I plan to give it a shot as my main desktop once it arrives, and that'll also give me some room to take this thing in to be fixed. I'm looking forward to that, since it'll be nice to not glare at my computer in annoyance all day.

Oh, and, since I mentioned Storage Spaces last time: since then, I took an old tower Mac Pro and installed FreeNAS in it, and it's a delight. FreeNAS is cool and I legitimately missed working with FreeBSD. So now I have that thing handling mass storage, while my gaming PC runs Windows Server and hosts my various development VMs using Hyper-V.

Other Misc. Tools

In my last post, I mentioned Franz as a coordinated tool for the ridiculous number of chat apps I had. I ended up settling on it and, other than some consistent problems with notifications and audio, it does a good-enough job. Certainly, it's a much-better experience than running all of those stupid apps individually, that's for sure.

I also did indeed make the switch from SourceTree to Tower. While I appreciated the $0 price of SourceTree, its general crashiness and tendency to hang on complex operations got to me and I've been using Tower ever since. It does exactly what it's supposed to and does it well. Nice job, Tower.

Browser-wise, I used Firefox Developer Edition for a while, but switched back to Safari when Firefox developed a tendency to somehow hard-crash my computer. I don't know if it's a computer-specific thing (likely related to whatever the fan trouble is, I'd guess) or trouble with Firefox, but it's not exactly the sort of thing I want to have to diagnose. Safari is speedy and I'm getting used to the developer tools, so it's a fine replacement.

For mail, I (and my company) have been using Spark for a while, and it's great. The ability to share emails and have inline threads among people in your organization is wonderful.

For calendars, I use Fantastical. I miss the days when I didn't have to really care about calendars at all, but, given that I regularly have meetings, this does a splendid job dealing with them. I quite appreciate the recent additions of weather reports and the automatic detection of meeting URLs, too.

For blogging, I use MarsEdit, both because it's good on its own and because writing an API for my blog meant I didn't need to bother making a good editing UI on my own. I think it's also just conceptually good to use tools that work with open protocols like that.

Getting Started with Hotwire in a Java Webapp

Jan 12, 2021, 5:19 PM

Whenever I have a great deal of discretion over how a web app is made these days, I like to push to see how simple I can make the front end portion. I spend some of my client time writing heavy client-JS front ends in React and Angular and what-have-you, and, though I get why they are good, I kind of hate them all.

One of the manifestations of my desires has been this very blog, where I set out to try not only some interesting current tools on the Java side, but also challenged myself heavily to use little to no JavaScript. On that front, I was tremendously successful - and, in fact, the only JavaScript on here is the Turbolinks library, which intercepts same-app links and updates the changed parts inline, without the server knowing about the "partial refresh" going on.

Since then, Turbolinks merged with its cousin Stimulus and apotheosized into Hotwire, which is somewhere in between a JavaScript framework and a manifesto. Specifically, it's a manifesto to my liking, so I've been champing at the bit to use it more.

Hotwire Overview

The "Hotwire" name is a cheeky truncation of HTML-over-the-wire, which itself is a neologism for how the web has historically worked: your server sends HTML, and then your browser does stuff with that. It "needs" a new name to set it apart from full-JS apps, which amount to basically sending an application to the browser, having it initialize the app, and then having the app do what would otherwise be the server's job by way of shuttling JSON around.

Turbo is that part that subsumed Turbolinks, and it focuses on enhancing existing HTML and providing a few web components to bring single-page-application niceties to server-rendered apps. The "Drive" part is Turbolinks, so that was familiar to me. What interested me next was Turbo Frames.

Turbo Frames

If you've ever used the XPages Dojo Tab Container's partialRefresh property before, Turbo Frames will be familiar. There are two main ways you can go about using it: making a "frame" that contains some navigable content (say, a form) that will then refresh in-place or making a lazy-loaded frame that pulls from another URL. The latter is what interested me now, and is what carries similar benefits to the Tab Container. It lets you serve the main page and then defer complex complication of an inner part without having to write your own JavaScript to do an API call or otherwise populate the section.

In my case, I wanted to do something very similar to the example. I have my main page, then a sidebar that can be potentially complicated to generate. So, I set up a Turbo Frame using this bit of JSP:

1
<turbo-frame id="links" src="${pageContext.request.contextPath}/links"></turbo-frame>

The only difference from the example, really, is the bit of EL in ${...}, which just makes sure that the final URL adapts to wherever the app is hosted.

The "links" resource there is another MVC controller that renders a different JSP page, truncated like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<html>
    <head>
        <script type="text/javascript" src="${pageContext.request.contextPath}/webjars/hotwired__turbo/7.0.0-beta.2/dist/turbo.es5-umd.js"></script>
    </head>
    <body>
        <turbo-frame id="links">
            <!-- expensive content here -->
        </turbo-frame>
    </body>
</html>

The <turbo-frame id="links"> on the initiating page matches up with the one in the embedded page to figure out what to extract and render.

One little side note here is my use of WebJars to bring in Turbo. This isn't an NPM-based project, so there's no package.json bringing the dependency in, but I also didn't want to just paste the JS into my project. Fortunately, WebJars does yeoman's work: it makes various JS libraries available in Servlet-friendly Java JAR format, giving you a JAR with the JS from whatever the library is in META-INF/resources. In turn, an at-least-reasonably-modern servlet container will serve files up from there as if they're part of your main app. That way, you can just use a Maven dependency and not have to worry.

A Hitch: 406 Not Acceptable

Edit 2021-01-13: Thanks to a new release of Turbo, this workaround is no longer needed.

When I first put this together, I saw that Turbo was doing its job of fetching from the remote URL, but it was getting a 406 Not Acceptable response from the server. It took me a minute to figure out why - the URL was correct, it was just a normal GET request, and nothing immediately stood out as a problem in the headers.

It turned out that the trouble was in the Accept header. To work with other Turbo components, Frames makes a request with a header like Accept: text/html; turbo-stream, text/html, application/xhtml+xml. That first one - text/html; turbo-stream - is problematic. I'm not sure if it's the presence of a qualifier at all on text/html, the space, or the lack of an = (as in text/html;charset=UTF-8), but Liberty didn't like it.

Since I'm not (yet, at least) using Turbo Streams, I decided to filter this out on the server. Since MVC is built on JAX-RS, I wrote a JAX-RS request filter to find any Accept values of this type and strip them out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Provider
@PreMatching
public class TurboStreamAcceptFilter implements ContainerRequestFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        MultivaluedMap<String, String> headers = requestContext.getHeaders();
        if(headers.containsKey(HttpHeaders.ACCEPT)) {
            List<String> cleaned = headers.get(HttpHeaders.ACCEPT).stream()
                .map(accept -> {
                    String[] vals = accept.split(",\\s*"); //$NON-NLS-1$
                    List<String> localClean = Arrays.stream(vals)
                        .filter(val -> val.indexOf(';') < 0)
                        .collect(Collectors.toList());
                    return String.join(", ", localClean); //$NON-NLS-1$
                })
                .collect(Collectors.toList());
            headers.put(HttpHeaders.ACCEPT, cleaned);
        }
    }
}

Since those filters happen before almost anything else, this cleared up the trouble.

Summary

Setting the Accept quirk aside, this was a pleasant success, and I look forward to using this more. I've found the modern Java stack of JAX-RS + CDI + MVC + simple JSP to be a delight, and Hotwire slots perfectly-smoothly into it. I still quire enjoy rendering HTML on the server and the associated perk of not having to duplicate business logic on both sides. Next time I have an app that requires a bit of actual JavaScript, I'll likely throw Stimulus into the mix here.