This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-11-13
Channels
- # announcements (1)
- # babashka (21)
- # beginners (39)
- # calva (10)
- # cljsrn (4)
- # clojure (133)
- # clojure-europe (27)
- # clojure-nl (2)
- # clojure-uk (1)
- # clojurescript (10)
- # code-reviews (19)
- # cursive (1)
- # data-science (1)
- # deps-new (2)
- # emacs (8)
- # events (4)
- # exercism (3)
- # fulcro (14)
- # introduce-yourself (2)
- # lsp (7)
- # malli (2)
- # meander (2)
- # missionary (6)
- # pathom (6)
- # pedestal (1)
- # releases (1)
- # remote-jobs (1)
- # sci (18)
- # tools-build (2)
i'm kind of new to Maven, and I'm trying to setup my current Java project to also compile some .clj code on src/clojure
Do you have some specific :gen-class
stuff?
do I have to add gen-class? I only want to have the namespace on the classpath, as it is only called by other clojure code (that is dynamically loaded)
I don't need to call it from Java
If the src
folder is on the classpath, Clojure code can require
it.
No compilation needed.
The Clojure code you are calling from Java is already on your classpath, right?
You only need :gen-class
in Clojure to create Java classes that you're going to call from Java. As long as you're using the Clojure Java API, you don't need :gen-class
and you can just require
code.
ok so I just need to add it as a resources on pom.xml?
the clojure source folder
If you know the root folder in advance, it can go in pom.xml
to be on the classpath when you build/run.
I thought you said you didn't know that folder until runtime tho?
I will have 1 clj file bundled in the .jar
that the dynamically loaded ones will call
OK, so you're constructing a classloader with that folder on it?
so to add the folder I do it as a resource?
<resources>
<resource>
<directory>src/main/clojure</directory>
</resource>
</resources>
I don't know Maven well enough but I think that will only affect building not running?
At work, we construct a classloader in our legacy app and add all of the Clojure code/libs to it. Not sure if it will help you -- since it is not Java -- but it might give you some guidance: https://github.com/framework-one/cfmljure/blob/master/framework/cfmljure.cfc#L138-L150
that part I got working
OK, so I'm not sure what else you need?
That classloader is going to contain the folder(s) that Clojure will load .clj
files from.
I think it's finding the file but I'm getting an error
Unable to resolve symbol: .iterator in this context
the dynamically loaded .clj uses a macro defined in the packaged .clj that uses .iterator
You'll need to share more detail about it.
.iterator
as a symbol doesn't sound right -- that's meant to be Java interop, calling the iterator
method, yes?
So maybe your problem is that your Clojure code isn't actually valid in the first place?
the code dynamically loaded at runtime (test_plugin.clj)
(ns test-plugin
(:use [plugin-tools]))
(feed SomeStream
(map dostuff))
the code packaged (plugin_tools.clj)
(ns plugin-tools
(:import (some.java.namespace DataSource)
(java.util.stream Stream)))
(defn seq1 [i]
(lazy-seq
(when (.hasNext i)
(cons (.next i) (seq1 i)))))
(defmacro feed [input & body]
`(with-open [stream# (-> ~input
DataSource/getInstance
.create)]
(->> stream#)
.iterator
seq1
~@body
dorun))
when the plugin_tools were on the same folder as test_plugin it was working ok
ok, there's actually a typo
i'm sorry
ok, everything working now 🙂
thanks and sorry
also, thank you for your contribution to this community!
using some of your stuff at work
Glad you got it working.
One thing I will caution about: don't use single segment namespaces. Namespaces compile to Java classes and Java has some weird restrictions on class names without packages.
Better to use qualified namespaces (and therefore nested folders and files).
One of the really nice things about loading Clojure into an existing/legacy apps is that you can specify a JVM option at startup and the Clojure runtime will spawn a Socket REPL, so you can connect your editor into your running legacy app and inspect it (and modify the Clojure code and maybe the global state)... magic!
hm, how does that work? what JVM option?
You can specify that JVM option for any JVM process that causes Clojure to be loaded and when Clojure loads, it will start a Socket REPL.
ah cool, I'll try that out
right now I'm fighting a weird bug, when the code reloads after I make a change, it complains about not finding a random symbol that is actually part of a string inside the code
(ns sample-feed
(:use [tcp-feed-tools])
(:import (my.namespace RedisPro)
(my.namespace MergedV4FeedObject)))
(def registrations #{"Bar" "Baz"})
(def in-reg-list? #(registrations (.getReg ^MergedV4FeedObject %)))
(feed MergedV4FeedObject
(filter in-reg-list?)
(map #(.toString %))
(RedisPro/send "feed-merged"))
The only thing I did was add "Foo" at the beginning of the registrations set, and it complains with
Unable to resolve symbol: ged in this context
which coincidentally are the last 3 characters of the string at the end
so maybe it's the macro's fault, but it's weird because the macro itself isn't being reloaded
and it works fine on the first load
hmm, it seems I can't get the socket REPL to work, not sure when it is supposed to boot up (when I load clojure code for the first time?)
How did you test it?
If you've provided that JVM property the REPL should start when the Clojure runtime is initialized.
Is there a benefit to using the component
library if none of my components are stateful (yet)?
If your components have any dependencies on each other, it's worth doing. And, yes, definitely worth doing for DB stuff.
next.jdbc
has built in support for Component for pooled datasources.
It supports both HikariCP and c3p0 out of the box.
See https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.2.737/api/next.jdbc.connection#component
Is there an example for postgres? I don't think it has a clazz
with a setJdbcUrl()
I don't understand that question. There's nothing DB-specific about the component
function. You provide a db-spec hash map, just like you would to get-datasource
(except for the :user
/`:username` weirdness with HikariCP).
(just use component
instead of ->pool
to get a Component).
There's an example of using component
further down in that section with HikariCP.
(connection/->pool com.zaxxer.hikari.HikariConfig ...
I have to specify the driver class, no?
:dbtype "postgresql" :dbname "..." :username "..." :password "..."
-- a db-spec.
No driver class needed.
You only ever need to specify the driver class if next.jdbc
doesn't know about your database type.
> Is there a benefit to using the `component` library if none of my components are stateful (yet)?
Predictably you'll have a number of IO-heavy components: db, cache, email, http... one of the main selling points is being able to mock out those (whereas with-redefs
is far from being a clean alternative)
Not sure if those count as 'stateful', guess it depends on who you ask :)
Also, protocol extension via metadata deserves a honorable mention, this way you can code impls with defn
instead of defrecord
methods. Less nesting, more specing
hikari isn't a database driver, it's a jdbc connection pool. I get it now.
Well, actually, I guess the DB connection has to be stateful as it needs to persist (they're expensive).
can confirm, i thought i was making my life easier with :dynamic
vars that held a bunch of connection context. it's now impossible for me to handle a dropped backend connection without restarting my entire app and fixing that is probably an entire day of work. just make a context map and some helper methods to generate a new one, you'll like it a lot more in the end
:dynamic is an even bigger trap than singletons, lol
I'm loading some .clj code at runtime from a Java application
(ns sample-feed
(:use [tcp-feed-tools])
(:import (my.namespace RedisPro)
(my.namespace MergedV4FeedObject)))
(def ids #{"Bar" "Baz"})
(def in-ids-list? #(ids (.getId ^MergedV4FeedObject %)))
(feed MergedV4FeedObject
(filter in-ids-list?)
(map #(.toString %))
(RedisPro/send "feed-merged"))
the first time it loads it works fine, however, when I add "Foo" to the ids set, on reload I get an error
Unable to resolve symbol: ged in this context
Which, coincidentally, are the last 3 characters of the string in the last line.
It only happens when I'm adding stuff, which leads me to believe when I call clojure.core/require
from the Java side it's expecting the same file length as beforebut on the next try it is able to load it
this happens even if the change to the file happens way before it tries to reload it
The way loading works is a top level form is read, compiled, then run, then the next is read compiled and run, etc etc
So if you have a top level form which does some long running thing, load the file, it won't read the rest of the file until that long running whatever completes
So if you modify file while the long running thing is running, up until the long running thing executes, you are reading the first version, and then afterwards you are reading the second
Which can lead to all kinds of weird behaviors (reading starting the middle of a form, etc)
I've played a lot of games with starting repls in weird places in code, and hit this a lot (popping a repl that hangs and waits for input is the equivalent of a long running process)
This is one of the reasons why you shouldn't have top level forms that do stuff, they should just define functions that do stuff, so you load the file in one quick go, then look up defined functions and call them after loafing
alright I'll give that a try, thanks
I'm starting my Java application with -Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl} but I can't connect to the repl
If you haven't caused the clojure runtime to load, it won't be loaded and won't start the repl
I'm loading the clojure runtime via the java clojure api
Clojure.var("clojure.core", "require")
and then trying telnet 127.0.0.1 5555
intellij with maven runner
"C:\Program Files\Java\jdk-17\bin\java.exe" -Dmaven.multiModuleProjectDirectory=D:\code\dataprocessor "-Dclojure.server.repl={:port 5555 :accept clojure.core.server/repl}" etc.
if I remove the " from the configuration and put escaped quotes around the map
Wild west, who knows what those flags are actually getting passed to, and what kind of escaping is needed
it errors out saying it cant find class 5555
I'm doing exactly as it says in https://www.jetbrains.com/help/idea/maven-runner.html
tried all manners
no quotes, quotes on the map, quotes on the whole thing
quotes on -D and the map
I guess that actually depends on how the process is being launched, which again, wild west who knows
"C:\Program Files\Java\jdk-17\bin\java.exe" -Dmaven.multiModuleProjectDirectory=D:\code\dataprocessor "-Dclojure.server.repl=\"{:port 5555 :accept clojure.core.server/repl}\"" "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\lib\idea_rt.jar=56643:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2021.2\plugins\maven\lib\maven3\boot\plexus-classworlds.license" org.codehaus.classworlds.Launcher -Didea.version=2021.2 -Dexec.workingdir=D:\code\dataprocessor "-Dexec.args=-classpath %classpath com.dataserver.Main" "-Dexec.executable=C:\Program Files\Java\jdk-17\bin\java.exe" exec:exec
pretty much all the options are quoted
so these are options for maven runner JVM
not the JVM used for the application
I mean, I dunno, I haven't seriously used maven in maybe 8 years, and never used intellij, but that is what all those command line options look like
That is why java.exe is mentioned twice, once as the java used to launch maven and a second time as the java used to launch your project code
ok I found it
still not the correct place
that's argv for the program, not VM options
ok finally
thanks 🙂
com.zaxxer/HickariCP
has a default logger that is preferred by compojure
and unfortunately it fails with a NOOP
logger (see https://stackoverflow.com/questions/31371993/basic-logging-in-clojure-web-service-not-appearing-on-console). Does anyone have an idea of how to ix this for HickariCP
in particular? If I switch to the logback-classic
package, I get this very overzealous logger that prints a lot more than what the default compojure
logger does.
Logging in Java is a giant mess. After a lot of messing about we've gone to log4j2 with everything else bridged into it and then we can control it easily via a Properties file (and we can select different files at startup via a JVM property.)
sort of on the same boat. we are using log4j, but barely. we actually build our own json payloads and log4j just provides the tooling to dump those into files that are rotated based on datetime and log levels
Ditto, though I ended up standardising on using tools.logging
from my own code, with SLF4J
(and logback-classic
) as the backing implementation. This is mostly because I found that (at least circa 2014 when I last spent appreciable time on this) SLF4J
had the most comprehensive support for forcing the dumpster fire of Java logging facades and implementation calls into using a single implementation (of my choice) at runtime.
This is as simple as https://github.com/pmonks/futbot/blob/main/deps.edn#L25-L27, executing https://github.com/pmonks/futbot/blob/main/src/futbot/config.clj#L35-L37, and https://github.com/pmonks/futbot/blob/main/resources/logback.xml`logback-classic`https://github.com/pmonks/futbot/blob/main/resources/logback.xml`SLF4J`https://github.com/pmonks/futbot/blob/main/resources/logback.xml.
well that blew in our faces really hard 😄
I was glad we keep dependencies up to date and were on 2.14.1 so we could apply the JVM option to disable lookups and restart everything, to tide us over to our next deployment (which has 2.15.0).
I'm on my phone so I can't share the deps we use for that.
It's no rush, I'll just deal with this verbose logger for now. Better than no logging!
And we use tools.logging and a JVM option to tell it to use log4j2 as well