3

I am attempting to use the gcloud library.

(ns firengine.state (:import [com.google.cloud AuthCredentials] [com.google.cloud.datastore DatastoreOptions])) (-> (DatastoreOptions/builder) (.projectId "<project_id>") (.authCredentials (AuthCredentials/createForJson (clojure.java.io/input-stream service-account-path))) .build) 

The above clojure code is translated from the following code snippet (ellided, click on "Run elsewhere").

import com.google.cloud.AuthCredentials; import com.google.cloud.datastore.DatastoreOptions; DatastoreOptions options = DatastoreOptions.builder() .projectId(PROJECT_ID) .authCredentials(AuthCredentials.createForJson( new FileInputStream(PATH_TO_JSON_KEY))).build(); 

When I call this code from the Clojure REPL, I get the following error.

Unhandled java.lang.IllegalArgumentException Can't call public method of non-public class: public com.google.cloud.ServiceOptions$Builder com.google.cloud.ServiceOptions$Builder.projectId(java.lang.String) Reflector.java: 88 clojure.lang.Reflector/invokeMatchingMethod Reflector.java: 28 clojure.lang.Reflector/invokeInstanceMethod boot.user4590132375374459695.clj: 168 firengine.state/eval17529 boot.user4590132375374459695.clj: 167 firengine.state/eval17529 Compiler.java: 6927 clojure.lang.Compiler/eval ... elided ... 

The com.google.cloud.datastore.DatastoreOptions code can be found here.

Updated June 29, 2016: Pursuant to Schlomi's advice below, I thought that maybe if I AOT compiled my own wrapper around DatastoreOptions that it would work.

(ns firengine.datastore (:import [com.google.cloud AuthCredentials] [com.google.cloud.datastore Datastore DatastoreOptions Entity Key KeyFactory]) (:gen-class :state state :init init :constructors {[String String] []})) (defn -init [^String project-id ^String service-account-path] (let [service-account (clojure.java.io/input-stream service-account-path) credentials (AuthCredentials/createForJson service-account) dsoptions (-> (DatastoreOptions/builder) (.projectId project-id) (.authCredentials credentials) .build)] [[] {:project-id project-id :service-account-path service-account-path :datastore-options dsoptions}])) 

I modified my boot development task to include the following:

(deftask development "Launch Immediate Feedback Development Environment" [] (comp (aot :namespace '#{firengine.datastore}) (repl :port 6800) (reload) (watch) (cljs) (target :dir #{"target"}))) 

And I attempted to construct the object like so:

(def service-account-path (System/getenv "FIRENGINE_SERVICE_ACCOUNT_PATH")) (def project-id (System/getenv "PROJECT_ID")) (def datastore-options (firengine.datastore. project-id service-account-path)) 

Unfortunately, I still get the same error?

 clojure.lang.Compiler$CompilerException: java.lang.reflect.InvocationTargetException, compiling:(state.clj:15:1) java.lang.reflect.InvocationTargetException: java.lang.IllegalArgumentException: Can't call public method of non-public class: public com.google.cloud.ServiceOptions$Builder com.google.cloud.ServiceOptions$Builder.projectId(java.lang.String) 

Am I not really aot compiling firengine.datastore?

4 Answers 4

4

Yeah.... that problem. You wouldnt believe it, but its actually an open bug in the jdk from ... wait for it ... 1999!

You can read about it more in clojure jira and on google groups.

You might have to make your own java wrapper to avoid this, or ask the library author to take this old known java bug into consideration.

If you dont want to write your own java wrapper, and the author insists that "this is the best design, like, ever!", then you could always force it by setting the method accessibility with (.setAccessible method true) and some more custom reflection code..

Good luck!

Sign up to request clarification or add additional context in comments.

Comments

1

As Shlomi said, that's a long running bug. Following the advice of Noam Ben Ari on the clj-jira, I've managed the circumvent the issue by writing a small java class that wraps the client creation. I can then use that directly from my clj code.

package pubsub_echo.pubsub; import com.google.cloud.AuthCredentials; import com.google.cloud.pubsub.PubSub; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class GCloudPubSub { public PubSub getClient() throws FileNotFoundException, IOException { PubSubOptions.Builder optionsBuilder = PubSubOptions.builder(); ClassLoader classLoader = getClass().getClassLoader(); FileInputStream authStream = new FileInputStream(classLoader.getResource("SERVICE_ACCOUNT.json").getPath()); AuthCredentials creds = AuthCredentials.createForJson(authStream); return optionsBuilder .authCredentials(creds) .projectId("PROJECT_ID") .build() .service(); } } 

For guidance on adding Java compilation to your project:

https://github.com/technomancy/leiningen/blob/master/doc/MIXED_PROJECTS.md

Comments

1

so, i am a complete Clojure noob and ran into a similar error using Caffeine Cache: "IllegalArgumentException Can't call public method of non-public class: public default void com.github.benmanes.caffeine.cache.LocalManualCache.put(java.lang.Object,java.lang.Object) clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:88)"

my original function signature was this:

(defn get [cache key] (.getIfPresent cache key) ) 

and i think the issue is that Clojure could not figure out where to apply Cache's .getIfPresent function. Adding the type fixed it:

(defn get [^Cache cache key] (.getIfPresent cache key) ) 

Even though it's not a direct answer and your question went over my head, I hope this helps.

Comments

0

Completely inspired by hironroy's answer, I worked through an isolated example of getting this working. Created the file below in src/gcloud/GcloduDatastore.java in the following github project.

package gcloud; import com.google.cloud.AuthCredentials; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.DatastoreOptions; import com.google.cloud.datastore.DatastoreOptions.Builder; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class GCloudDatastore { public static Datastore getDatastore() throws FileNotFoundException, IOException { DatastoreOptions.Builder optionsBuilder = DatastoreOptions.builder(); FileInputStream authStream = new FileInputStream(System.getenv("SERVICE_ACCOUNT_DOT_JSON_PATH")); AuthCredentials creds = AuthCredentials.createForJson(authStream); return optionsBuilder .authCredentials(creds) .projectId(System.getenv("PROJECT_ID")) .build() .service(); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.