-
Notifications
You must be signed in to change notification settings - Fork 1
/
agent.clj
120 lines (107 loc) · 5.46 KB
/
agent.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
;;;; Summary: The agent logic that directs the injections.
;;;; Author: Arnout Roemers
;;;;
;;;; The Java agent generated by this namespace 'gluer.GluerAgent' contains the
;;;; logic to intercept class loading and adding the injections to these classes
;;;; where applicable. The agent does not check everything beforehand, except
;;;; for parse errors in the .gluer specifications. The namespace 'gluer.clauses'
;;;; is used for the generating code that gets injected.
(ns gluer.agent
(:require [gluer.logic :as l]
[gluer.resources :as r]
[gluer.runtime :as runtime]
[gluer.config :as c]
[clojure.string :as s])
(:use [gluer.clauses]
[gluer.logging])
(:gen-class :name gluer.GluerAgent
:main false
:methods [^:static [premain [String java.lang.instrument.Instrumentation] void]]))
;;; Helper functions.
(defn- build-transformation-library
"Based on an association library, builds a map with a fully qualified class
name as key and a set of associations that transform that class as a value.
For example: {\"test.NativeClient\" #{{:where ... :what ...} ...}}"
[association-library]
(apply merge-with (comp set concat)
(for [association association-library]
(let [transforms (transforms-classes association)]
(zipmap transforms (repeat (hash-set association)))))))
;;; The transformation functions.
(defn- generate-retrieval
"Based on a association and the generated code concerning the <what> clause,
this function returns code that retrieves the adapter from the runtime.
Currently, the code generated depends on whether a <using> clause is specified
in the association."
[association what-code]
(let [where-type (type-of-where (:where association))]
(if-let [using (:using association)]
(format "gluer.Runtime.adapt(%1$s, Class.forName(\"%2$s\"), \"%3$s\")"
what-code where-type (get-in using [:class :word]))
(format "gluer.Runtime.adapt(%1$s, Class.forName(\"%2$s\"))"
what-code where-type))))
(defn- inject-associations
"Generates the code for the <what> clause per the specified associations and
injects this code based on the <where> clause in the javassist CtClass."
[ctclass associations]
(doseq [association associations]
(log-verbose "Injecting association:" association)
(let [what-code (generate-what association)
retrieval-code (generate-retrieval association what-code)]
(inject-where (:where association) ctclass retrieval-code))))
(defn- transform
"Checks whether the specified class needs injection transformation(s), based
on the supplied transformation library. If so, the new byte-code of the class
is returned, nil otherwise."
[class-name transformation-library]
(when-let [associations (transformation-library class-name)]
(log-verbose "Transforming class:" class-name)
(let [ctclass (r/class-by-name class-name)]
(inject-associations ctclass associations)
(log-verbose "Done transforming class"
(str class-name ", will now be loaded in the JVM."))
(.toBytecode ctclass))))
;;; The entry point of the agent and the transformer it adds to the class loading.
(defn transformer
"This reifies a ClassFileTransformer for use in the JVM class loading
instrumentation. The returned instance uses the transform function for its
functionallity."
[transformation-library]
(reify java.lang.instrument.ClassFileTransformer
(transform [this loader class-name class-being-redefined protection-domain classfile-buffer]
(try
(transform (s/replace class-name "/" ".") transformation-library)
(catch Throwable t (println "[ERROR]" t) (System/exit 1))))))
(defn -premain
"The main entry point for the agent. For more information, see
http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html."
[agent-args instrumentation]
;; Load the configuration.
(if (nil? agent-args)
(do (println "Please supply a configuration file.")
(System/exit 1))
(try
(let [config (c/read-config (slurp agent-args))]
(with-redefs [*verbose* (:verbose config)]
(log-verbose "Parsing .gluer files and searching for Adapters...")
;; Parse the files and check for parse errors.
(let [parsed (r/parse-gluer-files (:glue config))
erroneous (filter (comp :error :parsed) parsed)]
(if (not (empty? erroneous))
(do (doseq [error erroneous]
(println (format "Error parsing file '%s': %s"
(:file-name error)
(:error (:parsed error)))))
(System/exit 1))
;; No parse errors, so build the libraries and initialise the agent.
(let [association-library (r/build-association-library (map :parsed parsed))
transformation-library (build-transformation-library association-library)
adapter-library (r/build-adapter-library)
transformer (transformer transformation-library)]
(log-verbose "Transformation library:" transformation-library)
(log-verbose "Adapter library:" adapter-library)
(runtime/initialise adapter-library)
(.addTransformer instrumentation transformer))))))
(catch java.io.FileNotFoundException fnfe
(println "Configuration file" agent-args "not found.")
(System/exit 1)))))