├── .gitignore ├── test └── leiningen │ └── test │ └── ring.clj ├── src └── leiningen │ ├── ring │ ├── server_headless.clj │ ├── uberjar.clj │ ├── jar.clj │ ├── util.clj │ ├── server.clj │ ├── uberwar.clj │ └── war.clj │ └── ring.clj ├── project.clj ├── README.md └── LICENSE.html /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | lib 5 | classes 6 | /target 7 | /.lein-* 8 | -------------------------------------------------------------------------------- /test/leiningen/test/ring.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.test.ring 2 | (:use leiningen.ring :reload) 3 | (:use clojure.test)) 4 | 5 | (deftest replace-me ;; FIXME: write 6 | (is false "No tests have been written.")) 7 | -------------------------------------------------------------------------------- /src/leiningen/ring/server_headless.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.server-headless 2 | (:use leiningen.ring.server)) 3 | 4 | (defn server-headless 5 | "Start a Ring server without opening a browser." 6 | ([project] 7 | (server-task project {:open-browser? false})) 8 | ([project port] 9 | (server-task project {:port (Integer. port), :open-browser? false}))) 10 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-ring "0.8.13" 2 | :description "Leiningen Ring plugin" 3 | :url "https://github.com/weavejester/lein-ring" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.2.1"] 7 | [org.clojure/data.xml "0.0.6"] 8 | [leinjacker "0.4.1"]] 9 | :eval-in-leiningen true) 10 | -------------------------------------------------------------------------------- /src/leiningen/ring/uberjar.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.uberjar 2 | (:use [leiningen.ring.util :only (ensure-handler-set!)] 3 | [leiningen.ring.server :only (add-server-dep)]) 4 | (:require [leiningen.ring.jar :as jar] 5 | [leiningen.clean :as clean] 6 | leiningen.uberjar)) 7 | 8 | (defn- assoc-profile [project profile-name key value] 9 | (let [profile (get-in project [:profiles profile-name])] 10 | (assoc-in project [:profiles profile-name] 11 | (cond 12 | (vector? profile) (conj profile {key value}) 13 | (map? profile) (assoc profile key value) 14 | (nil? profile) {key value})))) 15 | 16 | (defn- no-uberjar-clean [project] 17 | "Modifies the uberjar profile so that no auto-clean is performed 18 | when leiningen takes control, thus avoiding the autogenerated main files 19 | from being wiped." 20 | (vary-meta project assoc-profile :uberjar :auto-clean false)) 21 | 22 | (defn uberjar 23 | "Create an executable $PROJECT-$VERSION.jar file with dependencies." 24 | [project] 25 | (ensure-handler-set! project) 26 | (when (:auto-clean project true) 27 | (clean/clean project)) 28 | (let [project (-> project add-server-dep jar/add-main-class no-uberjar-clean)] 29 | (jar/compile-main project) 30 | (leiningen.uberjar/uberjar project))) 31 | -------------------------------------------------------------------------------- /src/leiningen/ring.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring 2 | (:use [leiningen.help :only (help-for subtask-help-for)] 3 | [leiningen.ring.server :only (server)] 4 | [leiningen.ring.server-headless :only (server-headless)] 5 | [leiningen.ring.jar :only (jar)] 6 | [leiningen.ring.uberjar :only (uberjar)] 7 | [leiningen.ring.war :only (war)] 8 | [leiningen.ring.uberwar :only (uberwar)])) 9 | 10 | (defn- nary? [v n] 11 | (some #{n} (map count (:arglists (meta v))))) 12 | 13 | (defn ring 14 | "Manage a Ring-based application." 15 | {:help-arglists '([server server-headless war uberwar jar uberjar]) 16 | :subtasks [#'server #'server-headless #'war #'uberwar #'jar #'uberjar]} 17 | ([project] 18 | (println (if (nary? #'help-for 2) 19 | (help-for project "ring") 20 | (help-for "ring")))) 21 | ([project subtask & args] 22 | (case subtask 23 | "server" (apply server project args) 24 | "server-headless" (apply server-headless project args) 25 | "jar" (apply jar project args) 26 | "uberjar" (apply uberjar project args) 27 | "war" (apply war project args) 28 | "uberwar" (apply uberwar project args) 29 | (println "Subtask" (str \" subtask \") "not found." 30 | (subtask-help-for *ns* #'ring))))) 31 | -------------------------------------------------------------------------------- /src/leiningen/ring/jar.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.jar 2 | (:use [leiningen.ring.util :only (compile-form ensure-handler-set! update-project)] 3 | [leiningen.ring.server :only (add-server-dep)]) 4 | (:require [clojure.string :as str] 5 | leiningen.jar)) 6 | 7 | (defn default-main-namespace [project] 8 | (let [handler-sym (get-in project [:ring :handler])] 9 | (str (namespace handler-sym) ".main"))) 10 | 11 | (defn main-namespace [project] 12 | (or (get-in project [:ring :main]) 13 | (default-main-namespace project))) 14 | 15 | (defn compile-main [project] 16 | (let [main-ns (symbol (main-namespace project)) 17 | options (-> (select-keys project [:ring]) 18 | (assoc-in [:ring :open-browser?] false) 19 | (assoc-in [:ring :stacktraces?] false) 20 | (assoc-in [:ring :auto-reload?] false))] 21 | (compile-form project main-ns 22 | `(do (ns ~main-ns 23 | (:require ring.server.leiningen) 24 | (:gen-class)) 25 | (defn ~'-main [] 26 | (ring.server.leiningen/serve '~options)))))) 27 | 28 | (defn add-main-class [project] 29 | (update-project project assoc :main (symbol (main-namespace project)))) 30 | 31 | (defn jar 32 | "Create an executable $PROJECT-$VERSION.jar file." 33 | [project] 34 | (ensure-handler-set! project) 35 | (let [project (-> project add-server-dep add-main-class)] 36 | (compile-main project) 37 | (leiningen.jar/jar project))) 38 | -------------------------------------------------------------------------------- /src/leiningen/ring/util.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.util 2 | (:use [leinjacker.eval :only (eval-in-project)]) 3 | (:require [clojure.string :as str] 4 | [clojure.java.io :as io] 5 | leiningen.deps)) 6 | 7 | (defn ensure-handler-set! 8 | "Ensure the :handler option is set in the project map." 9 | [project] 10 | (when-not (-> project :ring :handler) 11 | (println 12 | (str "Missing Ring :handler option in project map.\n\n" 13 | "You need to have a line in your project.clj file that looks like:\n" 14 | " :ring {:handler your.app/handler}")) 15 | (System/exit 1))) 16 | 17 | (defn source-file [project namespace] 18 | (io/file (:compile-path project) 19 | (-> (str namespace) 20 | (str/replace "-" "_") 21 | (str/replace "." java.io.File/separator) 22 | (str ".clj")))) 23 | 24 | (defn compile-form 25 | "Compile the supplied form into the target directory." 26 | [project namespace form] 27 | ;; We need to ensure that deps has already run before we write anything 28 | ;; to :target-dir, which is otherwise cleaned by deps if it runs for 29 | ;; the first time as a side effect of eval-in-project Ideally, 30 | ;; generated sources would be going into a dedicated directory and thus 31 | ;; be immune from the lifecycle around :target-dir; that would be 32 | ;; straightforward using lein 2.x middlewares, but not so easy with 1.x. 33 | (leiningen.deps/deps project) 34 | (let [out-file (source-file project namespace)] 35 | (.mkdirs (.getParentFile out-file)) 36 | (with-open [out (io/writer out-file)] 37 | (binding [*out* out] (prn form)))) 38 | (eval-in-project project 39 | `(do (clojure.core/compile '~namespace) nil) 40 | nil)) 41 | 42 | (defn update-project 43 | "Update the project map using a function." 44 | [project func & args] 45 | (vary-meta 46 | (apply func project args) 47 | update-in [:without-profiles] #(apply func % args))) 48 | 49 | (defn source-and-resource-paths 50 | "Return a distinct sequence of the project's source and resource paths, 51 | unless :omit-source is true, in which case return only resource paths." 52 | [project] 53 | (let [resource-paths (concat [(:resources-path project)] (:resource-paths project)) 54 | source-paths (if (:omit-source project) 55 | '() 56 | (concat [(:source-path project)] (:source-paths project)))] 57 | (distinct (concat source-paths resource-paths)))) 58 | -------------------------------------------------------------------------------- /src/leiningen/ring/server.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.server 2 | (:require [leinjacker.deps :as deps] 3 | [leiningen.core.classpath :as classpath] 4 | [clojure.java.io :as io]) 5 | (:use [leinjacker.eval :only (eval-in-project)] 6 | [leiningen.ring.util :only (ensure-handler-set! update-project)])) 7 | 8 | (defn classpath-dirs 9 | "list of all dirs on the leiningen classpath" 10 | [project] 11 | (filter 12 | #(.isDirectory (io/file %)) 13 | (classpath/get-classpath project))) 14 | 15 | (defn load-namespaces 16 | "Create require forms for each of the supplied symbols. This exists because 17 | Clojure cannot load and use a new namespace in the same eval form." 18 | [& syms] 19 | `(require 20 | ~@(for [s syms :when s] 21 | `'~(if-let [ns (namespace s)] 22 | (symbol ns) 23 | s)))) 24 | 25 | (defn reload-paths [project] 26 | (or (get-in project [:ring :reload-paths]) 27 | (classpath-dirs project))) 28 | 29 | (defn add-dep [project dep] 30 | (update-project project deps/add-if-missing dep)) 31 | 32 | (defn add-server-dep [project] 33 | (add-dep project '[ring-server/ring-server "0.3.1"])) 34 | 35 | (defn start-server-expr [project] 36 | `(ring.server.leiningen/serve '~(select-keys project [:ring]))) 37 | 38 | (defn nrepl? [project] 39 | (-> project :ring :nrepl :start?)) 40 | 41 | (defn add-optional-nrepl-dep [project] 42 | (if (nrepl? project) 43 | (add-dep project '[org.clojure/tools.nrepl "0.2.3"]) 44 | project)) 45 | 46 | (defn start-nrepl-expr [project] 47 | (let [port (-> project :ring :nrepl (:port 0))] 48 | `(let [{port# :port} (clojure.tools.nrepl.server/start-server :port ~port)] 49 | (doseq [port-file# ["target/repl-port" ".nrepl-port"]] 50 | (-> port-file# 51 | java.io.File. 52 | (doto .deleteOnExit) 53 | (spit port#))) 54 | (println "Started nREPL server on port" port#)))) 55 | 56 | (defn server-task 57 | "Shared logic for server and server-headless tasks." 58 | [project options] 59 | (ensure-handler-set! project) 60 | (let [project (-> project 61 | (assoc-in [:ring :reload-paths] (reload-paths project)) 62 | (update-in [:ring] merge options))] 63 | (eval-in-project 64 | (-> project add-server-dep add-optional-nrepl-dep) 65 | (if (nrepl? project) 66 | `(do ~(start-nrepl-expr project) ~(start-server-expr project)) 67 | (start-server-expr project)) 68 | (load-namespaces 69 | 'ring.server.leiningen 70 | (if (nrepl? project) 'clojure.tools.nrepl.server) 71 | (-> project :ring :handler) 72 | (-> project :ring :init) 73 | (-> project :ring :destroy))))) 74 | 75 | (defn server 76 | "Start a Ring server and open a browser." 77 | ([project] 78 | (server-task project {})) 79 | ([project port] 80 | (server-task project {:port (Integer. port)}))) 81 | -------------------------------------------------------------------------------- /src/leiningen/ring/uberwar.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.uberwar 2 | (:use leiningen.ring.util) 3 | (:require [leiningen.ring.war :as war] 4 | [leinjacker.deps :as deps] 5 | [leiningen.compile :as compile] 6 | [clojure.java.io :as io] 7 | [leinjacker.utils :as lju]) 8 | (:import [java.util.jar JarFile JarEntry])) 9 | 10 | (defn default-uberwar-name [project] 11 | (or (get-in project [:ring :uberwar-name]) 12 | (:uberjar-name project) 13 | (str (:name project) "-" (:version project) "-standalone.war"))) 14 | 15 | (defn get-classpath [project] 16 | (if-let [get-cp (resolve 'leiningen.core.classpath/get-classpath)] 17 | (get-cp project) 18 | (->> (:library-path project) io/file .listFiles (map str)))) 19 | 20 | (defn contains-entry? [^java.io.File file ^String entry] 21 | (with-open [jar-file (JarFile. file)] 22 | (some (partial = entry) 23 | (map #(.getName ^JarEntry %) 24 | (enumeration-seq (.entries jar-file)))))) 25 | 26 | (defn jar-dependencies [project] 27 | (for [pathname (get-classpath project) 28 | :let [file (io/file pathname) 29 | fname (.getName file)] 30 | :when (and (.endsWith fname ".jar") 31 | ;; Servlet container will have it's own servlet-api impl 32 | (not (contains-entry? file "javax/servlet/Servlet.class")))] 33 | file)) 34 | 35 | (defn jar-entries [war project] 36 | (doseq [jar-file (jar-dependencies project)] 37 | (let [dir-path (.getParent jar-file) 38 | war-path (war/in-war-path "WEB-INF/lib/" dir-path jar-file)] 39 | (war/file-entry war project war-path jar-file)))) 40 | 41 | (defn write-uberwar [project war-path] 42 | (with-open [war-stream (war/create-war project war-path)] 43 | (doto war-stream 44 | (war/str-entry "WEB-INF/web.xml" (war/make-web-xml project)) 45 | (war/dir-entry project "WEB-INF/classes/" (:compile-path project))) 46 | (doseq [path (source-and-resource-paths project) 47 | :when path] 48 | (war/dir-entry war-stream project "WEB-INF/classes/" path)) 49 | (doseq [path (war/war-resources-paths project)] 50 | (war/dir-entry war-stream project "" path)) 51 | (jar-entries war-stream project))) 52 | 53 | (defn unmerge-profiles [project] 54 | (if-let [unmerge-fn (and (= 2 (lju/lein-generation)) 55 | (lju/try-resolve 'leiningen.core.project/unmerge-profiles))] 56 | (unmerge-fn project [:default]) 57 | project)) 58 | 59 | (defn uberwar 60 | "Create a $PROJECT-$VERSION.war with dependencies." 61 | ([project] 62 | (uberwar project (default-uberwar-name project))) 63 | ([project war-name] 64 | (ensure-handler-set! project) 65 | (let [project (-> project 66 | unmerge-profiles 67 | war/add-servlet-dep) 68 | result (compile/compile project)] 69 | (when-not (and (number? result) (pos? result)) 70 | (let [war-path (war/war-file-path project war-name)] 71 | (war/compile-servlet project) 72 | (if (war/has-listener? project) 73 | (war/compile-listener project)) 74 | (write-uberwar project war-path) 75 | (println "Created" war-path) 76 | war-path))))) 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lein-Ring 2 | 3 | Lein-Ring is a [Leiningen][1] plugin that automates common [Ring][2] 4 | tasks. 5 | 6 | It provides commands to start a development web server, and to turn a 7 | Ring handler into a standard war file. 8 | 9 | [1]: https://github.com/technomancy/leiningen 10 | [2]: https://github.com/ring-clojure/ring 11 | 12 | 13 | ## Install 14 | 15 | To use Lein-Ring, add it as a plugin to your `project.clj` file or 16 | your global profile: 17 | 18 | :plugins [[lein-ring "0.8.13"]] 19 | 20 | Then add a new `:ring` key to your `project.clj` file that contains a 21 | map of configuration options. At minimum there must be a `:handler` 22 | key that references your Ring handler: 23 | 24 | :ring {:handler hello-world.core/handler} 25 | 26 | When this is set, you can use Lein-Ring's commands. 27 | 28 | ## General options 29 | 30 | As well as the handler, you can specify several additional options via 31 | your `project.clj` file: 32 | 33 | * `:init` - 34 | A function to be called once before your handler starts. It should 35 | take no arguments. If you've compiled your Ring application into a 36 | war-file, this function will be called when your handler servlet is 37 | first initialized. 38 | 39 | * `:destroy` - 40 | A function called before your handler exits or is unloaded. It 41 | should take no arguments. If your Ring application has been compiled 42 | into a war-file, then this will be called when your hander servlet 43 | is destroyed. 44 | 45 | * `:adapter` - 46 | A map of options to be passed to the Ring adapter. This has no 47 | effect if you're deploying your application as a war-file. 48 | 49 | 50 | ## Environment variables 51 | 52 | Lein-Ring pays attention to several environment variables, including: 53 | 54 | * `PORT` - the port the web server uses for HTTP 55 | * `SSLPORT` - the port the web server uses for HTTPS 56 | 57 | These will override any options specified in the `project.clj` file, 58 | but won't override any options specified at the command line. 59 | 60 | 61 | ## Starting a web server 62 | 63 | The following command will start a development web server, and opens a 64 | web browser to the root page: 65 | 66 | lein ring server 67 | 68 | If the `LEIN_NO_DEV` environment variable is **not** set, the server 69 | will monitor your source directory for file modifications, and any 70 | altered files will automatically be reloaded. 71 | 72 | By default, this command attempts to find a free port, starting at 73 | 3000, but you can specify your own port as an argument: 74 | 75 | lein ring server 4000 76 | 77 | The server-headless command works like the server command, except that 78 | it doesn't open a web browser: 79 | 80 | lein ring server-headless 81 | 82 | lein ring server-headless 4000 83 | 84 | 85 | ## Web server options 86 | 87 | The following options affect the behavior of the web server started by 88 | `lein ring server`: 89 | 90 | * `:port` - The server port or port range 91 | 92 | * `:stacktraces?` - 93 | If true, display a stacktrace when an exception is thrown. 94 | Defaults to true in development mode, false in production. 95 | 96 | * `:auto-reload?` - 97 | If true, automatically reload modified source files. Defaults to 98 | true in development mode, false in production. 99 | 100 | * `:reload-paths` - 101 | A collection of directory paths that can trigger a reload. By 102 | default this takes all directories in the project classpath. 103 | 104 | * `:auto-refresh?` - 105 | If true, automatically refresh the browser when source or resource 106 | files are modified. Defaults to false. 107 | 108 | * `:nrepl` - 109 | A map of `:start?` and (optionally) `:port` keys. If `:start?` is true, 110 | open up an nREPL server on the given port. `:start?` defaults to false, 111 | `:port` defaults to an arbitrary free port. 112 | 113 | ## Executable jar files 114 | 115 | Lein-Ring can generate executable jar files for deployment purposes: 116 | 117 | lein ring uberjar 118 | 119 | This generates a jar file with all dependencies. You can then copy the 120 | file to your web server and execute it with: 121 | 122 | java -jar --standalone.jar 123 | 124 | 125 | ## War files 126 | 127 | ### Compiling 128 | 129 | Lein-Ring can generate war files that can be loaded onto legacy Java 130 | web services such as Apache Tomcat: 131 | 132 | lein ring war 133 | 134 | A servlet class and web.xml file will be generated automatically, and 135 | your application packaged up in a war file. 136 | 137 | Like the `lein jar` command, you can specify the filename being 138 | generated as an additional option: 139 | 140 | lein ring war my-app.war 141 | 142 | Also provided is a `lein ring uberwar` command, which packages up all 143 | the dependencies into the war: 144 | 145 | lein ring uberwar 146 | 147 | The following war-specific options are supported: 148 | 149 | * `:war-exclusions` - 150 | A list of regular expressions for excluding files from the target 151 | war. Defaults to excluding hidden files. 152 | 153 | * `:servlet-class` - 154 | The servlet class name. 155 | 156 | * `:servlet-name` - 157 | The name of the servlet (in web.xml). Defaults to the handler name. 158 | 159 | * `:url-pattern` - 160 | The url pattern of the servlet mapping (in web.xml). Defaults to "/*". 161 | 162 | * `:servlet-path-info?` - 163 | If true, a `:path-info` key is added to the request map. Defaults to true. 164 | 165 | * `:listener-class` - 166 | Class used for servlet init/destroy functions. Called listener 167 | because underneath it uses a ServletContextListener. 168 | 169 | * `:web-xml` - 170 | web.xml file to use in place of auto-generated version (relative to project root). 171 | 172 | * `:servlet-version` - 173 | The version of the servlet spec that we claim to conform 174 | to. Attributes corresponding to this version will be added to the 175 | web-app element of the web.xml. If not specified, defaults to 2.5. 176 | 177 | * `:uberwar-name` - 178 | The name of the file generated by lein ring uberwar. 179 | 180 | These keys should be placed under the `:ring` key in `project.clj`, 181 | and are optional values. If not supplied, default values will be used instead. 182 | 183 | ### Resources 184 | 185 | A war file can also include additional resource files, such as images or 186 | stylesheets. These should be placed in the directory specified by the 187 | Leiningen `:resources-path` key, which defaults to "resources". These 188 | resources will be placed on the classpath. To include multiple directories, 189 | use the Leiningen `:resource-paths` key, which should be a vector. The 190 | values in `:resources-path` and `:resource-paths` will be concatenated. 191 | 192 | However, there is another sort of resource, one accessed through the 193 | `ServletContext` object. These resources are usually not on the classpath, 194 | and are instead placed in the root of the war file. If you happen to need this 195 | functionality, you can place your files in the directory specified by the 196 | `:war-resources-path` key, which defaults to "war-resources". (As with 197 | normal resources, here you can use `:war-resource-paths` to include multiple 198 | directories.) It's recommended that you only use WAR resources for 199 | compatibility with legacy Java interfaces; under most circumstances, you 200 | should use the normal `:resources-path` instead. 201 | -------------------------------------------------------------------------------- /src/leiningen/ring/war.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.ring.war 2 | (:require [leiningen.compile :as compile] 3 | [clojure.java.io :as io] 4 | [clojure.string :as string] 5 | [leinjacker.utils :as lju] 6 | [leinjacker.deps :as deps]) 7 | (:use [clojure.data.xml :only [sexp-as-element indent-str]] 8 | leiningen.ring.util) 9 | (:import [java.util.jar Manifest 10 | JarEntry 11 | JarOutputStream] 12 | [java.io BufferedOutputStream 13 | FileOutputStream 14 | ByteArrayInputStream])) 15 | 16 | (defn default-war-name [project] 17 | (or (get-in project [:ring :war-name]) 18 | (str (:name project) "-" (:version project) ".war"))) 19 | 20 | (defn war-file-path [project war-name] 21 | (let [target-dir (or (:target-dir project) (:target-path project))] 22 | (.mkdirs (io/file target-dir)) 23 | (str target-dir "/" war-name))) 24 | 25 | (defn skip-file? [project war-path file] 26 | (or (re-find #"^\.?#" (.getName file)) 27 | (re-find #"~$" (.getName file)) 28 | (some #(re-find % war-path) 29 | (get-in project [:ring :war-exclusions] [#"(^|/)\."])))) 30 | 31 | (defn- to-byte-stream [^String s] 32 | (ByteArrayInputStream. (.getBytes s))) 33 | 34 | (def default-ring-manifest 35 | {"Created-By" "Leiningen Ring Plugin" 36 | "Built-By" (System/getProperty "user.name") 37 | "Build-Jdk" (System/getProperty "java.version")}) 38 | 39 | (defn make-manifest [user-manifest] 40 | (Manifest. 41 | (to-byte-stream 42 | (reduce 43 | (fn [accumulated-manifest [k v]] 44 | (str accumulated-manifest "\n" k ": " v)) 45 | "Manifest-Version: 1.0" 46 | (merge default-ring-manifest user-manifest))))) 47 | 48 | (defn default-servlet-class [project] 49 | (let [handler-sym (get-in project [:ring :handler]) 50 | ns-parts (-> (namespace handler-sym) 51 | (string/replace "-" "_") 52 | (string/split #"\.") 53 | (butlast) 54 | (vec) 55 | (conj "servlet"))] 56 | (string/join "." ns-parts))) 57 | 58 | (defn servlet-class [project] 59 | (or (get-in project [:ring :servlet-class]) 60 | (default-servlet-class project))) 61 | 62 | (defn servlet-ns [project] 63 | (-> (servlet-class project) 64 | (string/replace "_" "-"))) 65 | 66 | (defn servlet-name [project] 67 | (or (get-in project [:ring :servlet-name]) 68 | (str (get-in project [:ring :handler]) 69 | " servlet"))) 70 | 71 | (defn has-listener? [project] 72 | (let [ring-options (:ring project)] 73 | (or (contains? ring-options :init) 74 | (contains? ring-options :destroy)))) 75 | 76 | (defn default-listener-class [project] 77 | (let [listener-sym (or (get-in project [:ring :init]) 78 | (get-in project [:ring :destroy])) 79 | ns-parts (-> (namespace listener-sym) 80 | (string/replace "-" "_") 81 | (string/split #"\.") 82 | (butlast) 83 | (vec) 84 | (conj "listener"))] 85 | (string/join "." ns-parts))) 86 | 87 | (defn listener-class [project] 88 | (or (get-in project [:ring :listener-class]) 89 | (default-listener-class project))) 90 | 91 | (defn listener-ns [project] 92 | (-> (listener-class project) 93 | (string/replace "_" "-"))) 94 | 95 | (defn url-pattern [project] 96 | (or (get-in project [:ring :url-pattern]) 97 | "/*")) 98 | 99 | (def web-app-attrs 100 | "Attributes for the web-app element, indexed by the servlet version." 101 | {"2.4" {:xmlns "http://java.sun.com/xml/ns/j2ee" 102 | :xmlns:xsi "http://www.w3.org/2001/XMLSchema-instance" 103 | :xsi:schemaLocation (str "http://java.sun.com/xml/ns/j2ee " 104 | "http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd") 105 | :version "2.4"} 106 | "2.5" {:xmlns "http://java.sun.com/xml/ns/javaee" 107 | :xmlns:xsi "http://www.w3.org/2001/XMLSchema-instance" 108 | :xsi:schemaLocation (str "http://java.sun.com/xml/ns/javaee " 109 | "http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd") 110 | :version "2.5"} 111 | "3.0" {:xmlns "http://java.sun.com/xml/ns/javaee" 112 | :xmlns:xsi "http://www.w3.org/2001/XMLSchema-instance" 113 | :xsi:schemaLocation (str "http://java.sun.com/xml/ns/javaee " 114 | "http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd") 115 | :version "3.0"}}) 116 | 117 | (def default-servlet-version "2.5") 118 | 119 | (defn make-web-xml [project] 120 | (let [ring-options (:ring project)] 121 | (if (contains? ring-options :web-xml) 122 | (slurp (:web-xml ring-options)) 123 | (indent-str 124 | (sexp-as-element 125 | [:web-app 126 | (get web-app-attrs 127 | (get-in project [:ring :servlet-version] default-servlet-version) 128 | {}) 129 | (if (has-listener? project) 130 | [:listener 131 | [:listener-class (listener-class project)]]) 132 | [:servlet 133 | [:servlet-name (servlet-name project)] 134 | [:servlet-class (servlet-class project)]] 135 | [:servlet-mapping 136 | [:servlet-name (servlet-name project)] 137 | [:url-pattern (url-pattern project)]]]))))) 138 | 139 | (defn generate-handler [project handler-sym] 140 | (if (get-in project [:ring :servlet-path-info?] true) 141 | `(fn [request#] 142 | (let [context# ^String (.getContextPath (:servlet-request request#))] 143 | (~handler-sym 144 | (assoc request# 145 | :context context# 146 | :path-info (-> (:uri request#) (subs (.length context#)) not-empty (or "/")))))) 147 | handler-sym)) 148 | 149 | (defn compile-servlet [project] 150 | (let [handler-sym (get-in project [:ring :handler]) 151 | handler-ns (symbol (namespace handler-sym)) 152 | servlet-ns (symbol (servlet-ns project))] 153 | (compile-form project servlet-ns 154 | `(do (ns ~servlet-ns 155 | (:require ring.util.servlet ~handler-ns) 156 | (:gen-class :extends javax.servlet.http.HttpServlet)) 157 | (ring.util.servlet/defservice 158 | ~(generate-handler project handler-sym)))))) 159 | 160 | (defn compile-listener [project] 161 | (let [init-sym (get-in project [:ring :init]) 162 | destroy-sym (get-in project [:ring :destroy]) 163 | init-ns (and init-sym (symbol (namespace init-sym))) 164 | destroy-ns (and destroy-sym (symbol (namespace destroy-sym))) 165 | project-ns (symbol (listener-ns project))] 166 | (compile-form project project-ns 167 | `(do (ns ~project-ns 168 | (:require ~@(set (remove nil? [init-ns destroy-ns]))) 169 | (:gen-class :implements [javax.servlet.ServletContextListener])) 170 | ~(let [servlet-context-event (gensym)] 171 | `(do 172 | (defn ~'-contextInitialized [this# ~servlet-context-event] 173 | ~(if init-sym 174 | `(~init-sym))) 175 | (defn ~'-contextDestroyed [this# ~servlet-context-event] 176 | ~(if destroy-sym 177 | `(~destroy-sym))))))))) 178 | 179 | (defn create-war [project file-path] 180 | (-> (FileOutputStream. file-path) 181 | (BufferedOutputStream.) 182 | (JarOutputStream. (make-manifest (:manifest project))))) 183 | 184 | (defn write-entry [war war-path entry] 185 | (.putNextEntry war (JarEntry. war-path)) 186 | (io/copy entry war)) 187 | 188 | (defn str-entry [war war-path content] 189 | (write-entry war war-path (to-byte-stream content))) 190 | 191 | (defn in-war-path [war-path root file] 192 | (str war-path 193 | (-> (.toURI (io/file root)) 194 | (.relativize (.toURI file)) 195 | (.getPath)))) 196 | 197 | (defn file-entry [war project war-path file] 198 | (when (and (.exists file) 199 | (.isFile file) 200 | (not (skip-file? project war-path file))) 201 | (write-entry war war-path file))) 202 | 203 | (defn dir-entry [war project war-root dir-path] 204 | (doseq [file (file-seq (io/file dir-path))] 205 | (let [war-path (in-war-path war-root dir-path file)] 206 | (file-entry war project war-path file)))) 207 | 208 | (defn war-resources-paths [project] 209 | (filter identity 210 | (distinct (concat [(:war-resources-path project "war-resources")] (:war-resource-paths project))))) 211 | 212 | (defn write-war [project war-path] 213 | (with-open [war-stream (create-war project war-path)] 214 | (doto war-stream 215 | (str-entry "WEB-INF/web.xml" (make-web-xml project)) 216 | (dir-entry project "WEB-INF/classes/" (:compile-path project))) 217 | (doseq [path (source-and-resource-paths project) 218 | :when path] 219 | (dir-entry war-stream project "WEB-INF/classes/" path)) 220 | (doseq [path (war-resources-paths project)] 221 | (dir-entry war-stream project "" path)) 222 | war-stream)) 223 | 224 | (defn unmerge-profiles [project] 225 | (if-let [unmerge-fn (and (= 2 (lju/lein-generation)) 226 | (lju/try-resolve 'leiningen.core.project/unmerge-profiles))] 227 | (unmerge-fn project [:default]) 228 | project)) 229 | 230 | (defn add-servlet-dep [project] 231 | (-> project 232 | (deps/add-if-missing '[ring/ring-servlet "1.2.1"]) 233 | (deps/add-if-missing '[javax.servlet/servlet-api "2.5"]))) 234 | 235 | (defn war 236 | "Create a $PROJECT-$VERSION.war file." 237 | ([project] 238 | (war project (default-war-name project))) 239 | ([project war-name] 240 | (ensure-handler-set! project) 241 | (let [project (-> project 242 | unmerge-profiles 243 | add-servlet-dep) 244 | result (compile/compile project)] 245 | (when-not (and (number? result) (pos? result)) 246 | (let [war-path (war-file-path project war-name)] 247 | (compile-servlet project) 248 | (if (has-listener? project) 249 | (compile-listener project)) 250 | (write-war project war-path) 251 | (println "Created" war-path) 252 | war-path))))) 253 | -------------------------------------------------------------------------------- /LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | --------------------------------------------------------------------------------