Clojure access Java Interface constants - clojure

I am trying to access the constants from this file in Clojure:
public interface CacheConstants
{
/** This is the name of the config file that we will look for by default. */
String DEFAULT_CONFIG = "/cache.ccf";
/** Delimiter of a cache name component. This is used for hierarchical deletion */
String NAME_COMPONENT_DELIMITER = ":";
}
Clojure attempt:
(import '[org.apache.jcs.engine.CacheConstants])
org.apache.jcs.engine.CacheConstants/DEFAULT_CONFIG
;; clojure.lang.Compiler$CompilerException: java.lang.ClassNotFoundException: org.apache.jcs.engine.CacheConstants
How do I access these two values?

The following works with a local java interface:
file: src-java/jroot/Const.java
package jroot;
public interface Const {
long ANSWER = 42;
file: project.clj
(defproject clj "0.1.0-SNAPSHOT"
<snip>
:java-source-paths ["src-java"]
)
Clojure code:
(ns tst.clj.core
(:require ...)
(:import [jroot Const])
)
(println Const/ANSWER)
---------
Const/ANSWER => 42
Note the space in the :import vector (very important).
Also, if the jar file is local use this syntax:
(defproject test-project "0.1.0-SNAPSHOT"
:description "Blah blah blah"
...
:resource-paths ["resources/Siebel.jar" "resources/SiebelJI_enu.jar"])

Related

How to import local Java class at Clojure REPL?

There are existing answers to similar questions, but they tend to use Maven, which is not an option for my project. Also, I have not found any which give concrete examples of the syntax you use to import at the repl, especially when the class is local as opposed to from the web.
I want to import a Java class into my Clojure project:
public class MyLocalClass1 {
int x;
String y;
public MyLocalClass1() {
this.x = 0;
this.y = "hello there";
}
public MyLocalClass1(int x, String y) {
this.x = x;
this.y = y;
}
public void setX(int x) {
this.x = x;
}
public void setY(String y) {
this.y = y;
}
public int getX() {
return x;
}
public String getY() {
return y;
}
}
I successfully built it in Idea as Java2Import.jar.
Here is project.clj, where I import the jar:
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
:url "https://www.eclipse.org/legal/epl-2.0/"}
:dependencies [[org.clojure/clojure "1.10.1"]]
:resource-paths ["/path/to/my/Java/project/Java2Import/artifacts/Java2Import/"]
:repl-options {:init-ns import-local-java-4.core})
However, when I try to import, I get errors:
import-local-java-4.core=> (import (Java2Import.Java2Import))
Syntax error macroexpanding clojure.core/import at (/tmp/form-init9909516591129619328.clj:1:1).
Java2Import.Java2Import - failed: #{(quote quote)} at: [:class :quoted-spec :quote] spec: :clojure.core.specs.alpha/quotable-import-list
() - failed: Insufficient input at: [:package-list :spec :classes] spec: :clojure.core.specs.alpha/package-list
Java2Import.Java2Import - failed: #{(quote quote)} at: [:package-list :quoted-spec :quote] spec: :clojure.core.specs.alpha/quotable-import-list
(Java2Import.Java2Import) - failed: simple-symbol? at: [:class :spec] spec: :clojure.core.specs.alpha/quotable-import-list
What am I doing wrong?
Clone this repo and look at the directory structure and also project.clj
https://github.com/io-tupelo/clj-java-template
The source code files look like so:
~/io-tupelo/clj-java-template > ls -1 **/*.{clj,java}
project.clj
src/clj/demo/core.clj
src/java/demo/Calc.java
test/clj/_bootstrap.clj
test/clj/tst/demo/core.clj
The test namespace shows the correct syntax:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[tupelo.string :as str])
(:import [demo Calc]))
(dotest
(is= 5 (add2 2 3)) ; from src/clj/demo/core.clj
(is= 42 (Calc/add2 29 13)) ; from src/java/demo/Calc.java
)
Run the unit tests in test/clj/tst/demo/core.clj:
~/io-tupelo/clj-java-template > lein clean ; lein test
Compiling 1 source files to /Users/alanthompson/io-tupelo/clj-java-template/target/default+test+test/class-files
lein test _bootstrap
-----------------------------------
Clojure 1.10.3 Java 17.0.2
-----------------------------------
lein test tst.demo.core
Ran 2 tests containing 2 assertions.
0 failures, 0 errors.
You don't need to compile the Java code into a JAR file. In face, it is much easier if you just use the Java source files.
You can also run in the REPL:
~/io-tupelo/clj-java-template > lein repl
Compiling 1 source files to /Users/alanthompson/io-tupelo/clj-java-template/target/default/class-files
demo.core=> (import '[demo Calc])
demo.Calc
demo.core=> (prn :result (Calc/add2 6 7))
:result 13
nil
Please note that the function call syntax of require and import is different in the REPL than the macro syntax of the (ns ...) form!
Also, any edits to your Java code will not be picked up in the REPL. You will need to exit and restart the REPL to force a Java recompile.
P.S.
As the README explains, I find it even better to use the lein-test-refresh plugin. IMHO it is like a REPL on steroids!
P.P.S.
If you only have a JAR file (i.e. not Java source code), you may wish to use the lein install command, which will copy the JAR file into the local Maven cache ~/.m2 where Leiningen can find it as normal.

Shared env vars between profiles when using yogthos/config in clojure?

The https://github.com/yogthos/config approach let's you lay out per-profile env variables in separate files, like the below , in a project.clj .
Per the below, one can use lein with-profile prod uberjar or lein with-profile dev repl and the like.
But my issue is I have been unable to figure out how to place some common values into a shared area, accessible by dev, stage, prod profiles.
Basic example
(defproject edn-config-test "0.1.0-SNAPSHOT"
...
:profiles {:shared {:resource-paths ["config/shared"]}
:dev {:resource-paths ["config/dev"]}
:stage {:resource-paths ["config/stage"]}
:prod {:resource-paths ["config/prod"]}}
...
(with files)
config/shared/config.edn
config/dev/config.edn
config/stage/config.edn
config/prod/config.edn
I tried this without luck
lein with-profile shared,prod lein , borrowing from the composite approach in
https://github.com/technomancy/leiningen/blob/stable/doc/PROFILES.md#composite-profiles
When I do that, I only get variables in prod profile, for example.
I think it is a limitation of config. I tried this (more explicit):
:profiles {:dev {:resource-paths ["config/shared" "config/dev"]}
:prod {:resource-paths [ "config/prod" "config/shared"]}}
However, the last file wins and the first is ignored. So for :dev the shared stuff is ignored, and for :prod the prod stuff is ignored (like it doesn't exist):
config/dev/config.edn => {:special-val :dev-val}
config/prod/config.edn => {:special-val :prod-val}
cat config/shared/config.edn => {:shared-val 42}
and results:
> lein with-profile prod run
(:shared-val env) => 42
(:special-val env) => nil
> lein with-profile dev run
(:shared-val env) => nil
(:special-val env) => :dev-val
Perhaps you'd like to submit an enhancement PR to the project?
Here is the problem. It uses io/resource to read config.edn, which implicitly expects there to be only one file config.edn anywhere on the classpath:
(defn- read-config-file [f]
(try
(when-let [url (io/resource f)]
(with-open [r (-> url io/reader PushbackReader.)]
(edn/read r))) ...
(read-config-file "config.edn")
So you'd have to get away from the hard-coded filename config.edn, and make something like config-dev.edn, config-prod.edn, and config-shared.edn. At least then they could all live in a single ./resources dir.

Is there a simple text encyptor for Clojure which just needs one password to decrypt?

Is there a simple text encyptor for Clojure which just needs one password to decrypt? I just want something like to encrypt:
(encrypt "Some secret message" "Some secret key")
:and to decrypt:
(decrypt (encrypt "Some secret message" "Some secret key") "Some secret key")
:will return:
"Some secret message"
I decided this functionality would be helpful for a project I am working on.
Aside from the standard clojure, it also requires commons-codec.
project.clj:
(defproject <project> "VERSION"
...
:dependencies [[org.clojure/clojure "1.5.1"]
[commons-codec "1.8"]])
<project>/src/crypt.clj:
(ns <whatever>.crypt
(:import (javax.crypto KeyGenerator SecretKey Cipher SecretKeyFactory)
(javax.crypto.spec SecretKeySpec PBEKeySpec)
(org.apache.commons.codec.binary Base64)))
(def ^:dynamic *salt* "BIND SALT IN APP")
(defn cipher- [] (. Cipher getInstance "AES"))
(defn aes-keyspec [rawkey] (new SecretKeySpec rawkey "AES"))
(defn encrypt-
[rawkey plaintext]
(let [cipher (cipher-)
mode (. Cipher ENCRYPT_MODE)]
(. cipher init mode (aes-keyspec rawkey))
(. cipher doFinal (. plaintext getBytes))))
(defn decrypt-
[rawkey ciphertext]
(let [cipher (cipher-)
mode (. Cipher DECRYPT_MODE)]
(. cipher init mode (aes-keyspec rawkey))
(new String(. cipher doFinal ciphertext))))
(defn passkey
[password & [iterations size]]
(let [keymaker (SecretKeyFactory/getInstance "PBKDF2WithHmacSHA1")
pass (.toCharArray password)
salt (.getBytes *salt*)
iterations (or iterations 1000)
size (or size 128)
keyspec (PBEKeySpec. pass salt iterations size)]
(-> keymaker (.generateSecret keyspec) .getEncoded)))
(defn encrypt
[password plaintext]
(encrypt- (passkey password) plaintext))
(defn decrypt
[password cyphertext]
(decrypt- (passkey password) cyphertext))
usage:
(binding [crypt/*salt* "THE SALT WE ARE USING"]
(crypt/encrypt "password" "message")
(crypt/decrypt "password" *1)))
if you can use generated keys, that is going to be more secure:
(defn aes-keygen [] (. KeyGenerator getInstance "AES"))
(defn genkey
[keygen]
(. keygen init 128)
(. (. keygen generateKey ) getEncoded))
(def generated (keygen aes-keygen))
(encrypt- generated plaintext)
(decrypt- generated *1)
This is only using vanilla security features provided with the jvm (commons-codec is just used for base-64 encoding/decoding so that we can operate on arbitrary input, it is not part of the security setup).
Try simple-crypto:
[org.clojars.tnoda/simple-crypto "0.1.0"]
It do exactly what you want:
user=> (use 'org.clojars.tnoda.simple-crypto)
user=> (decrypt (encrypt "Some secret message" "Some secret key!") "Some secret key!")
"Some secret message"
I'm not aware of any Clojure library that is doing that mostly -I would say- because it is too easy to do using javax.crypto, javax.crypto.spec and java.security packages.
Could be done within 30 lines.

Difference between lein repl (-main "something") and lein run "something")

Newbie Clojure and leiningen question:
Given the code snippet in my project below, this works from the lein repl :
==> (-main "something")
produces the expected "Command: something ... running ... done"
but doesn't work from the command line:
me pallet1]lein run "something"
produces "Command: something ... error: not resolved as a command"
Why? / how do I fix it?
To reproduce:
lein new eg
Then edit the generated project file, adding :main eg.core to define the main function, and edit the generated src/eg/core.clj file, and paste this in:
core.clj
(ns eg.core)
(defn something [] (println "Something!"))
(defn run-command-if-any [^String commandname]
(printf "Command: %s ..." commandname)
(if-let [cmd (ns-resolve *ns* (symbol commandname))]
(
(println "running ...") (cmd) (println "done.")
)
(println "error: not resolved as a command.")
))
(defn -main [ commandname ] (run-command-if-any commandname))
Then
lein repl
eg.core=> (-main "something")
works (ie prints "Something!) , but
lein run something
doesn't (ie prints the "error: not resolved" message)
The problem is that when you run it from lein your default namespace is "user" namespace:
(defn -main [ commandname ] (println *ns*))
Prints #<Namespace user>. So it doesn't contain something function because it is from another namespace. You have several choices:
Pass fully qualified function name: your-namespace/something instead of something.
Use your-namespace instead of *ns*: (ns-resolve 'your-namespace (symbol commandname))
Change namespace to your-namespace in -main.
Example of method 3:
(defn -main [ commandname ]
(in-ns 'your-namespace)
(run-command-if-any commandname))
Also you if you want to call several functions one by one you should use do:
(do (println "Hello")
(println "World"))
Not just braces like ( (println "hello") (println "World"))
the lein exec plugin is very useful for scripting such things in the context of a project. I have used this extensively for writing Jenkins jobs in clojure and other scripting situations
lein exec -pe '(something ...) (something-else) (save-results)'

Issue reading command-line-args in script

I'm attempting to write a library to do some domain-specific stuff. I'd like to add some scripts to this library that can be run straight from the commandline.
Directory layout:
+- project.clj
+- src/
| +- my-lib.clj
| +- my-lib/
| +- my-sub-1.clj
+- scripts/
+- run-command.sh
The src/my-lib.clj file loads the my-lib/my-sub-1.clj file:
(ns my-lib
(:use [my-lib.my-sub-1])
)
The src/my-lib/my-sub-1.clj file contains the functions I want to make available. One of these functions is called "convert" and takes two arguments: an input filename and an output filename; it converts the fileformat. To use it:
(convert "input-file.txt" "output-file.txt")
The (do-something-interesting) and (convert) functions in src/my-lib/my-sub-1.clj look like this:
(defn do-something-interesting
[input-file output-file]
(magic-happens-here input-file output-file))
(defn convert
[args]
(let [infile (first args)
outfile (second args)]
(do-something-interesting infile outfile)))
My goal at the moment: to create a script "run-command.sh" in the "scripts" directory that takes two arguments: the input filename and output filename. It should be possible to run the script with:
./run-command.sh input-file.txt output-file.txt
I do have run-command.sh working, as long as I hard-code the filenames in that script by using the (do-something-interesting) function instead of (convert). I haven't been able yet to read from the argument list...
The run-command.sh script that works:
#!/bin/sh
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(do-something-interesting "path-to-input-file" "path-to-output-file")
"
... but what doesn't work:
#!/bin/sh
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(convert *command-line-args*)
"
The error I get is:
Exception in thread "main" java.lang.IllegalArgumentException: No implementation of method: :reader of protocol: #'clojure.contrib.io/Streams found for class: nil (NO_SOURCE_FILE:0)
at clojure.lang.Compiler.eval(Compiler.java:5435)
at clojure.lang.Compiler.eval(Compiler.java:5386)
at clojure.core$eval.invoke(core.clj:2382)
at clojure.main$eval_opt.invoke(main.clj:235)
at clojure.main$initialize.invoke(main.clj:254)
at clojure.main$null_opt.invoke(main.clj:279)
at clojure.main$main.doInvoke(main.clj:354)
at clojure.lang.RestFn.invoke(RestFn.java:422)
at clojure.lang.Var.invoke(Var.java:369)
at clojure.lang.AFn.applyToHelper(AFn.java:165)
at clojure.lang.Var.applyTo(Var.java:482)
at clojure.main.main(main.java:37)
Caused by: java.lang.IllegalArgumentException: No implementation of method: :reader of protocol: #'clojure.contrib.io/Streams found for class: nil
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:471)
at clojure.contrib.io$eval32$fn__33$G__23__38.invoke(io.clj:118)
at bioclojure.vcf$header.invoke(vcf.clj:22)
at bioclojure.vcf$column_header.invoke(vcf.clj:55)
at bioclojure.vcf$column_names.invoke(vcf.clj:61)
at bioclojure.vcf$vcf2tsv.invoke(vcf.clj:169)
at bioclojure.vcf$convert.invoke(vcf.clj:185)
at user$eval474.invoke(NO_SOURCE_FILE:3)
at clojure.lang.Compiler.eval(Compiler.java:5419)
... 11 more
I've tried "use"ing clojure.contrib.io within the script file run-command.sh itself, and within the top library file my-lib.clj, but no luck so far...
If anyone could help me out that'd be great.
jan.
You should consider using the "gen-class" feature to handle command
line arguments in a compiled Clojure/Java function, instead of
on-the-fly evaluating Clojure code via Clojures main/repl function:
(ns commandline
(:gen-class))
(defn -main [& args]
(convert args))
Use lein jar to create the Jar file of your Application and pass
command line arguments in your shell skript to the main function:
#!/bin/bash
java -cp "../lib/*":../YOURPROJECT.jar:$CLASSPATH commandline "$#"
That's because you don't specify cli arguments. You have to call java .... clojure.main some-script.clj a b c. Then a, b and c will be contained in *command-line-args*.
I found the solution thanks to kotarak's suggestion. In run-command.sh I need to refer to the arguments using the bash-way instead of the clojure way. And I don't even need that separate (convert) function.
What the script looked like:
#!/bin/sh
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(convert *command-line-args*)
"
What it should look like:
#!/bin/sh
java -cp "../lib/*":"../src":$CLASSPATH clojure.main -e "
(use '[my-lib my-sub-1])
(do-something-interesting \"$1\" \"$2\")
"