/wheels/vendor/javaloader/JavaLoader.cfc

http://cfwheels.googlecode.com/ · ColdFusion CFScript · 500 lines · 381 code · 96 blank · 23 comment · 13 complexity · 1dc1f533428c713f5eb12c1b8a53dc90 MD5 · raw file

  1. <!--- Document Information -----------------------------------------------------
  2. Title: JavaLoader.cfc
  3. Author: Mark Mandel
  4. Email: mark@compoundtheory.com
  5. Website: http://www.compoundtheory.com
  6. Purpose: Utlitity class for loading Java Classes
  7. ------------------------------------------------------------------------------->
  8. <cfcomponent name="JavaLoader" hint="Loads External Java Classes, while providing access to ColdFusion classes">
  9. <cfscript>
  10. instance = StructNew();
  11. instance.static.uuid = "A0608BEC-0AEB-B46A-0E1E1EC5F3CE7C9C";
  12. </cfscript>
  13. <cfimport taglib="tags" prefix="jl">
  14. <!------------------------------------------- PUBLIC ------------------------------------------->
  15. <cffunction name="init" hint="Constructor" access="public" returntype="JavaLoader" output="false">
  16. <cfargument name="loadPaths" hint="An array of directories of classes, or paths to .jar files to load" type="array" default="#ArrayNew(1)#" required="no">
  17. <cfargument name="loadColdFusionClassPath" hint="Loads the ColdFusion libraries" type="boolean" required="No" default="false">
  18. <cfargument name="parentClassLoader" hint="(Expert use only) The parent java.lang.ClassLoader to set when creating the URLClassLoader" type="any" default="" required="false">
  19. <cfargument name="sourceDirectories" hint="Directories that contain Java source code that are to be dynamically compiled" type="array" required="No">
  20. <cfargument name="compileDirectory" hint="the directory to build the .jar file for dynamic compilation in, defaults to ./tmp" type="string" required="No" default="#getDirectoryFromPath(getMetadata(this).path)#/tmp">
  21. <cfargument name="trustedSource" hint="Whether or not the source is trusted, i.e. it is going to change? Defaults to false, so changes will be recompiled and loaded" type="boolean" required="No" default="false">
  22. <cfscript>
  23. initUseJavaProxyCFC();
  24. if(arguments.loadColdFusionClassPath)
  25. {
  26. //arguments.parentClassLoader = createObject("java", "java.lang.Thread").currentThread().getContextClassLoader();
  27. //can't use above, as doesn't work in some... things
  28. arguments.parentClassLoader = getPageContext().getClass().getClassLoader();
  29. //arguments.parentClassLoader = createObject("java", "java.lang.ClassLoader").getSystemClassLoader();
  30. //can't use the above, it doesn't have the CF stuff in it.
  31. }
  32. setClassLoadPaths(arguments.loadPaths);
  33. setParentClassLoader(arguments.parentClassLoader);
  34. ensureNetworkClassLoaderOnServerScope();
  35. loadClasses();
  36. if(structKeyExists(arguments, "sourceDirectories") AND ArrayLen(arguments.sourceDirectories))
  37. {
  38. setJavaCompiler(createObject("component", "JavaCompiler").init(arguments.compileDirectory));
  39. setSourceDirectories(arguments.sourceDirectories);
  40. setCompileDirectory(arguments.compileDirectory);
  41. setTrustedSource(arguments.trustedSource);
  42. compileSource();
  43. setSourceLastModified(calculateSourceLastModified());
  44. //do the method switching for non-trusted source
  45. if(NOT arguments.trustedSource)
  46. {
  47. variables.createWithoutCheck = variables.create;
  48. StructDelete(this, "create");
  49. StructDelete(variables, "create");
  50. this.create = variables.createWithSourceCheck;
  51. }
  52. }
  53. return this;
  54. </cfscript>
  55. </cffunction>
  56. <cffunction name="create" hint="Retrieves a reference to the java class. To create a instance, you must run init() on this object" access="public" returntype="any" output="false">
  57. <cfargument name="className" hint="The name of the class to create" type="string" required="Yes">
  58. <cfscript>
  59. try
  60. {
  61. //do this in one line just for speed.
  62. return createJavaProxy(getURLClassLoader().loadClass(arguments.className));
  63. }
  64. catch(java.lang.ClassNotFoundException exc)
  65. {
  66. throwException("javaloader.ClassNotFoundException", "The requested class could not be found.", "The requested class '#arguments.className#' could not be found in the loaded jars/directories.");
  67. }
  68. </cfscript>
  69. </cffunction>
  70. <cffunction name="getURLClassLoader" hint="Returns the com.compoundtheory.classloader.NetworkClassLoader in case you need access to it" access="public" returntype="any" output="false">
  71. <cfreturn instance.ClassLoader />
  72. </cffunction>
  73. <cffunction name="getVersion" hint="Retrieves the version of the loader you are using" access="public" returntype="string" output="false">
  74. <cfreturn "1.0">
  75. </cffunction>
  76. <!------------------------------------------- PACKAGE ------------------------------------------->
  77. <!------------------------------------------- PRIVATE ------------------------------------------->
  78. <cffunction name="createWithSourceCheck" hint="does the create call, but first makes a source check" access="private" returntype="any" output="false">
  79. <cfargument name="className" hint="The name of the class to create" type="string" required="Yes">
  80. <cfscript>
  81. var dateLastModified = calculateSourceLastModified();
  82. /*
  83. If the source has changed in any way, recompile and load
  84. */
  85. if(dateCompare(dateLastModified, getSourceLastModified()) eq 1)
  86. {
  87. loadClasses();
  88. compileSource();
  89. }
  90. //if all the comilation goes according to plan, set the date last modified
  91. setSourceLastModified(dateLastModified);
  92. return createWithoutCheck(argumentCollection=arguments);
  93. </cfscript>
  94. </cffunction>
  95. <cffunction name="loadClasses" hint="loads up the classes in the system" access="private" returntype="void" output="false">
  96. <cfscript>
  97. var iterator = getClassLoadPaths().iterator();
  98. var file = 0;
  99. var classLoader = 0;
  100. var networkClassLoaderClass = 0;
  101. var networkClassLoaderProxy = 0;
  102. networkClassLoaderClass = getServerURLClassLoader().loadClass("com.compoundtheory.classloader.NetworkClassLoader");
  103. networkClassLoaderProxy = createJavaProxy(networkClassLoaderClass);
  104. if(isObject(getParentClassLoader()))
  105. {
  106. classLoader = networkClassLoaderProxy.init(getParentClassLoader());
  107. }
  108. else
  109. {
  110. classLoader = networkClassLoaderProxy.init();
  111. }
  112. while(iterator.hasNext())
  113. {
  114. file = createObject("java", "java.io.File").init(iterator.next());
  115. if(NOT file.exists())
  116. {
  117. throwException("javaloader.PathNotFoundException", "The path you have specified could not be found", file.getAbsolutePath() & " does not exist");
  118. }
  119. classLoader.addUrl(file.toURL());
  120. }
  121. setURLClassLoader(classLoader);
  122. </cfscript>
  123. </cffunction>
  124. <cffunction name="compileSource" hint="compile dynamic source" access="private" returntype="void" output="false">
  125. <cfscript>
  126. var dir = 0;
  127. var path = 0;
  128. var paths = 0;
  129. var file = 0;
  130. var counter = 1;
  131. var len = 0;
  132. var directories = 0;
  133. //do check to see if the compiled jar is already there
  134. var jarName = calculateJarName(getSourceDirectories());
  135. var jar = getCompileDirectory() & "/" & jarName;
  136. </cfscript>
  137. <cfif fileExists(jar)>
  138. <cfif isTrustedSource()>
  139. <!--- add that jar to the classloader --->
  140. <cfset file = createObject("java", "java.io.File").init(jar)>
  141. <cfset getURLClassLoader().addURL(file.toURL())>
  142. <cfreturn />
  143. <cfelse>
  144. <cffile action="delete" file="#jar#"/>
  145. </cfif>
  146. </cfif>
  147. <cftry>
  148. <cfset path = getCompileDirectory() & "/" & createUUID()/>
  149. <cfdirectory action="create" directory="#path#">
  150. <cfscript>
  151. //first we copy the source to our tmp dir
  152. directories = getSourceDirectories();
  153. len = arraylen(directories);
  154. for(; counter lte len; counter = counter + 1)
  155. {
  156. dir = directories[counter];
  157. directoryCopy(dir, path);
  158. }
  159. //then we compile it, and grab that jar
  160. paths = ArrayNew(1); //have to write it this way so CF7 compiles
  161. ArrayAppend(paths, path);
  162. jar = getJavaCompiler().compile(paths, getURLClassLoader(), jarName);
  163. </cfscript>
  164. <!--- add that jar to the classloader --->
  165. <cfset file = createObject("java", "java.io.File").init(jar)>
  166. <cfset getURLClassLoader().addURL(file.toURL())>
  167. <!--- delete the files --->
  168. <cfif directoryExists(path)>
  169. <cfdirectory action="delete" recurse="true" directory="#path#">
  170. </cfif>
  171. <!--- save the file for when trusted source is on ---->
  172. <cfif fileExists(jar) AND NOT isTrustedSource()>
  173. <cffile action="delete" file="#jar#" />
  174. </cfif>
  175. <cfcatch>
  176. <!--- make sure the files are deleted --->
  177. <cfif directoryExists(path)>
  178. <cfdirectory action="delete" recurse="true" directory="#path#">
  179. </cfif>
  180. <cfrethrow>
  181. </cfcatch>
  182. </cftry>
  183. </cffunction>
  184. <cffunction name="calculateJarName" hint="returns the jar file name for a directory array" access="private" returntype="string" output="false">
  185. <cfargument name="directoryArray" hint="array of directories to compile" type="array" required="Yes">
  186. <cfscript>
  187. var file = hash(arrayToList(arguments.directoryArray)) & ".jar";
  188. return file;
  189. </cfscript>
  190. </cffunction>
  191. <cffunction name="calculateSourceLastModified" hint="returns what the source last modified was" access="private" returntype="date" output="false">
  192. <cfscript>
  193. var lastModified = createDate(1900, 1, 1);
  194. var dir = 0;
  195. var qLastModified = 0;
  196. var directories = getSourceDirectories();
  197. var len = arraylen(directories);
  198. var counter = 0;
  199. </cfscript>
  200. <!--- cf7 syntax. Yuck. --->
  201. <cfloop from="1" to="#len#" index="counter">
  202. <cfset dir = directories[counter]>
  203. <jl:directory action="list" directory="#dir#" recurse="true"
  204. type="file"
  205. sort="dateLastModified desc"
  206. name="qLastModified">
  207. <cfscript>
  208. //it's possible there are no source files.
  209. if(qLastModified.recordCount)
  210. {
  211. //get the latest date modified
  212. if(dateCompare(lastModified, qlastModified.dateLastModified) eq -1)
  213. {
  214. /*
  215. This is here, because cfdirectory only ever gives you minute accurate modified
  216. date, which is not good enough.
  217. */
  218. lastModified = createObject("java", "java.util.Date").init(createObject("java", "java.io.File").init(qLastModified.directory & "/" & qLastModified.name).lastModified());
  219. }
  220. }
  221. else
  222. {
  223. lastModified = Now();
  224. }
  225. </cfscript>
  226. </cfloop>
  227. <cfreturn lastModified />
  228. </cffunction>
  229. <cffunction name="ensureNetworkClassLoaderOnServerScope"
  230. hint="makes sure there is a URL class loader on the server scope that can load me up some networkClassLoader goodness"
  231. access="private" returntype="void" output="false">
  232. <cfscript>
  233. var Class = createObject("java", "java.lang.Class");
  234. var Array = createObject("java", "java.lang.reflect.Array");
  235. var jars = queryJars();
  236. var iterator = jars.iterator();
  237. var file = 0;
  238. var urls = Array.newInstance(Class.forName("java.net.URL"), ArrayLen(jars));
  239. var counter = 0;
  240. var urlClassLoader = 0;
  241. var key = instance.static.uuid & "." & getVersion();
  242. </cfscript>
  243. <cfif NOT StructKeyExists(server, key)>
  244. <cflock name="javaloader.networkclassloader" throwontimeout="true" timeout="60">
  245. <cfscript>
  246. if(NOT StructKeyExists(server, key))
  247. {
  248. while(iterator.hasNext())
  249. {
  250. Array.set(urls, counter, createObject("java", "java.io.File").init(iterator.next()).toURL());
  251. counter = counter + 1;
  252. }
  253. urlClassLoader = createObject("java", "java.net.URLClassLoader").init(urls);
  254. //put it on the server scope
  255. server[key] = urlClassLoader;
  256. }
  257. </cfscript>
  258. </cflock>
  259. </cfif>
  260. </cffunction>
  261. <cffunction name="createJavaProxy" hint="create a javaproxy, dependent on CF server settings" access="private" returntype="any" output="false">
  262. <cfargument name="class" hint="the java class to create the proxy with" type="any" required="Yes">
  263. <cfscript>
  264. return createObject("java", "coldfusion.runtime.java.JavaProxy").init(arguments.class);
  265. </cfscript>
  266. </cffunction>
  267. <cffunction name="createJavaProxyCFC" hint="create a javaproxy, dependent on CF server settings" access="private" returntype="any" output="false">
  268. <cfargument name="class" hint="the java class to create the proxy with" type="any" required="Yes">
  269. <cfscript>
  270. return createObject("component", "JavaProxy")._init(arguments.class);
  271. </cfscript>
  272. </cffunction>
  273. <cffunction name="initUseJavaProxyCFC" hint="initialise whether or not to use the JavaProxy CFC instead of the coldfusion java object" access="private" returntype="string" output="false">
  274. <cfscript>
  275. try
  276. {
  277. createObject("java", "coldfusion.runtime.java.JavaProxy");
  278. }
  279. catch(Object exc)
  280. {
  281. //do method replacement, as it will be much faster long term
  282. variables.createJavaProxy = variables.createJavaProxyCFC;
  283. }
  284. </cfscript>
  285. </cffunction>
  286. <cffunction name="queryJars" hint="pulls a query of all the jars in the /resources/lib folder" access="private" returntype="array" output="false">
  287. <cfscript>
  288. var qJars = 0;
  289. //the path to my jar library
  290. var path = getDirectoryFromPath(getMetaData(this).path) & "lib/";
  291. var jarList = "";
  292. var aJars = ArrayNew(1);
  293. var libName = 0;
  294. </cfscript>
  295. <cfdirectory action="list" name="qJars" directory="#path#" filter="*.jar" sort="name desc"/>
  296. <cfloop query="qJars">
  297. <cfscript>
  298. libName = ListGetAt(name, 1, "-");
  299. //let's not use the lib's that have the same name, but a lower datestamp
  300. if(NOT ListFind(jarList, libName))
  301. {
  302. ArrayAppend(aJars, path & "/" & name);
  303. jarList = ListAppend(jarList, libName);
  304. }
  305. </cfscript>
  306. </cfloop>
  307. <cfreturn aJars>
  308. </cffunction>
  309. <cffunction name="getClassLoadPaths" access="private" returntype="array" output="false">
  310. <cfreturn instance.classLoadPaths />
  311. </cffunction>
  312. <cffunction name="setClassLoadPaths" access="private" returntype="void" output="false">
  313. <cfargument name="classLoadPaths" type="array" required="true">
  314. <cfset instance.classLoadPaths = arguments.classLoadPaths />
  315. </cffunction>
  316. <cffunction name="getParentClassLoader" access="private" returntype="any" output="false">
  317. <cfreturn instance.parentClassLoader />
  318. </cffunction>
  319. <cffunction name="setParentClassLoader" access="private" returntype="void" output="false">
  320. <cfargument name="parentClassLoader" type="any" required="true">
  321. <cfset instance.parentClassLoader = arguments.parentClassLoader />
  322. </cffunction>
  323. <cffunction name="getServerURLClassLoader" hint="returns the server URL class loader" access="private" returntype="any" output="false">
  324. <cfreturn server[instance.static.uuid & "." & getVersion()] />
  325. </cffunction>
  326. <cffunction name="setURLClassLoader" access="private" returntype="void" output="false">
  327. <cfargument name="ClassLoader" type="any" required="true">
  328. <cfset instance.ClassLoader = arguments.ClassLoader />
  329. </cffunction>
  330. <cffunction name="hasJavaCompiler" hint="whether this object has a javaCompiler" access="private" returntype="boolean" output="false">
  331. <cfreturn StructKeyExists(instance, "javaCompiler") />
  332. </cffunction>
  333. <cffunction name="getJavaCompiler" access="private" returntype="JavaCompiler" output="false">
  334. <cfreturn instance.javaCompiler />
  335. </cffunction>
  336. <cffunction name="setJavaCompiler" access="private" returntype="void" output="false">
  337. <cfargument name="javaCompiler" type="JavaCompiler" required="true">
  338. <cfset instance.javaCompiler = arguments.javaCompiler />
  339. </cffunction>
  340. <cffunction name="getSourceDirectories" access="private" returntype="array" output="false">
  341. <cfreturn instance.sourceDirectories />
  342. </cffunction>
  343. <cffunction name="setSourceDirectories" access="private" returntype="void" output="false">
  344. <cfargument name="sourceDirectories" type="array" required="true">
  345. <cfset instance.sourceDirectories = arguments.sourceDirectories />
  346. </cffunction>
  347. <cffunction name="getSourceLastModified" access="private" returntype="date" output="false">
  348. <cfreturn instance.sourceLastModified />
  349. </cffunction>
  350. <cffunction name="setSourceLastModified" access="private" returntype="void" output="false">
  351. <cfargument name="sourceLastModified" type="date" required="true">
  352. <cfset instance.sourceLastModified = arguments.sourceLastModified />
  353. </cffunction>
  354. <cffunction name="hasSourceLastModified" hint="whether this object has a sourceLastModified" access="private" returntype="boolean" output="false">
  355. <cfreturn StructKeyExists(instance, "sourceLastModified") />
  356. </cffunction>
  357. <cffunction name="getCompileDirectory" access="private" returntype="string" output="false">
  358. <cfreturn instance.compileDirectory />
  359. </cffunction>
  360. <cffunction name="setCompileDirectory" access="private" returntype="void" output="false">
  361. <cfargument name="compileDirectory" type="string" required="true">
  362. <cfset instance.compileDirectory = arguments.compileDirectory />
  363. </cffunction>
  364. <cffunction name="throwException" access="private" hint="Throws an Exception" output="false">
  365. <cfargument name="type" hint="The type of exception" type="string" required="Yes">
  366. <cfargument name="message" hint="The message to accompany the exception" type="string" required="Yes">
  367. <cfargument name="detail" type="string" hint="The detail message for the exception" required="No" default="">
  368. <cfthrow type="#arguments.type#" message="#arguments.message#" detail="#arguments.detail#">
  369. </cffunction>
  370. <cffunction name="isTrustedSource" access="private" returntype="boolean" output="false">
  371. <cfreturn instance.isTrustedSource />
  372. </cffunction>
  373. <cffunction name="setTrustedSource" access="private" returntype="void" output="false">
  374. <cfargument name="isTrustedSource" type="boolean" required="true">
  375. <cfset instance.isTrustedSource = arguments.isTrustedSource />
  376. </cffunction>
  377. <!---
  378. Copies a directory.
  379. @param source Source directory. (Required)
  380. @param destination Destination directory. (Required)
  381. @param nameConflict What to do when a conflict occurs (skip, overwrite, makeunique). Defaults to overwrite. (Optional)
  382. @return Returns nothing.
  383. @author Joe Rinehart (joe.rinehart@gmail.com)
  384. @version 1, July 27, 2005
  385. --->
  386. <cffunction name="directoryCopy" access="private" output="true">
  387. <cfargument name="source" required="true" type="string">
  388. <cfargument name="destination" required="true" type="string">
  389. <cfargument name="nameconflict" required="true" default="overwrite">
  390. <cfset var contents = "" />
  391. <cfset var dirDelim = createObject("java", "java.lang.System").getProperty("file.separator")>
  392. <cfif not(directoryExists(arguments.destination))>
  393. <cfdirectory action="create" directory="#arguments.destination#">
  394. </cfif>
  395. <cfdirectory action="list" directory="#arguments.source#" name="contents">
  396. <cfloop query="contents">
  397. <cfif contents.type eq "file">
  398. <cffile action="copy" source="#arguments.source#/#name#" destination="#arguments.destination#/#name#" nameconflict="#arguments.nameConflict#">
  399. <cfelseif contents.type eq "dir">
  400. <cfset directoryCopy(arguments.source & dirDelim & name, arguments.destination & dirDelim & name) />
  401. </cfif>
  402. </cfloop>
  403. </cffunction>
  404. </cfcomponent>