001 package railo.loader.engine; 002 003 import java.io.BufferedOutputStream; 004 import java.io.EOFException; 005 import java.io.File; 006 import java.io.FileOutputStream; 007 import java.io.IOException; 008 import java.io.InputStream; 009 import java.io.OutputStream; 010 import java.io.PrintWriter; 011 import java.io.UnsupportedEncodingException; 012 import java.lang.reflect.InvocationTargetException; 013 import java.lang.reflect.Method; 014 import java.net.URL; 015 import java.net.URLDecoder; 016 import java.util.ArrayList; 017 import java.util.Arrays; 018 import java.util.Date; 019 import java.util.Iterator; 020 import java.util.List; 021 022 import javax.servlet.ServletConfig; 023 import javax.servlet.ServletException; 024 025 import railo.Version; 026 import railo.loader.TP; 027 import railo.loader.classloader.RailoClassLoader; 028 import railo.loader.util.ExtensionFilter; 029 import railo.loader.util.Util; 030 031 import com.intergral.fusiondebug.server.FDControllerFactory; 032 033 /** 034 * Factory to load CFML Engine 035 */ 036 public class CFMLEngineFactory { 037 038 // set to false to disable patch loading, for example in major alpha releases 039 private static final boolean PATCH_ENABLED = true; 040 041 private static CFMLEngineFactory factory; 042 private static File railoServerRoot; 043 private static CFMLEngineWrapper engineListener; 044 private CFMLEngine engine; 045 private ClassLoader mainClassLoader=new TP().getClass().getClassLoader(); 046 private int version; 047 private List<EngineChangeListener> listeners=new ArrayList<EngineChangeListener>(); 048 private File resourceRoot; 049 050 private PrintWriter out; 051 052 053 /** 054 * Constructor of the class 055 */ 056 protected CFMLEngineFactory(){ 057 } 058 059 /** 060 * returns instance of this factory (singelton-> always the same instance) 061 * do auto update when changes occur 062 * @param config 063 * @return Singelton Instance of the Factory 064 * @throws ServletException 065 */ 066 public static CFMLEngine getInstance(ServletConfig config) throws ServletException { 067 068 if(engineListener!=null) { 069 if(factory==null) factory=engineListener.getCFMLEngineFactory(); 070 return engineListener; 071 } 072 073 if(factory==null) factory=new CFMLEngineFactory(); 074 075 076 // read init param from config 077 factory.setInitParam(config); 078 079 CFMLEngine engine = factory.getEngine(); 080 engine.addServletConfig(config); 081 engineListener = new CFMLEngineWrapper(engine); 082 083 // add listener for update 084 factory.addListener(engineListener); 085 return engineListener; 086 } 087 088 /** 089 * returns instance of this factory (singelton-> always the same instance) 090 * do auto update when changes occur 091 * @return Singelton Instance of the Factory 092 * @throws RuntimeException 093 */ 094 public static CFMLEngine getInstance() throws RuntimeException { 095 if(engineListener!=null) return engineListener; 096 throw new RuntimeException("engine is not initalized, you must first call getInstance(ServletConfig)"); 097 } 098 099 /** 100 * used only for internal usage 101 * @param engine 102 * @throws RuntimeException 103 */ 104 public static void registerInstance(CFMLEngine engine) throws RuntimeException { 105 if(factory==null) factory=engine.getCFMLEngineFactory(); 106 107 // first update existing listener 108 if(engineListener!=null) { 109 if(engineListener.equalTo(engine, true)) return; 110 engineListener.onUpdate(engine);// perhaps this is still refrenced in the code, because of that we update it 111 factory.removeListener(engineListener); 112 } 113 114 // now register this 115 if(engine instanceof CFMLEngineWrapper) 116 engineListener=(CFMLEngineWrapper) engine; 117 else 118 engineListener = new CFMLEngineWrapper(engine); 119 120 factory.addListener(engineListener); 121 } 122 123 124 /** 125 * returns instance of this factory (singelton-> always the same instance) 126 * @param config 127 * @param listener 128 * @return Singelton Instance of the Factory 129 * @throws ServletException 130 */ 131 public static CFMLEngine getInstance(ServletConfig config, EngineChangeListener listener) throws ServletException { 132 getInstance(config); 133 134 // add listener for update 135 factory.addListener(listener); 136 137 // read init param from config 138 factory.setInitParam(config); 139 140 CFMLEngine e = factory.getEngine(); 141 e.addServletConfig(config); 142 143 // make the FDController visible for the FDClient 144 FDControllerFactory.makeVisible(); 145 146 return e; 147 } 148 149 void setInitParam(ServletConfig config) { 150 if(railoServerRoot!=null) return; 151 152 String initParam=config.getInitParameter("railo-server-directory"); 153 if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server-root"); 154 if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server-dir"); 155 if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server"); 156 initParam=Util.parsePlaceHolder(Util.removeQuotes(initParam,true)); 157 158 try { 159 if(!Util.isEmpty(initParam)) { 160 File root=new File(initParam); 161 if(!root.exists()) { 162 if(root.mkdirs()) { 163 railoServerRoot=root.getCanonicalFile(); 164 return; 165 } 166 } 167 else if(root.canWrite()) { 168 railoServerRoot=root.getCanonicalFile(); 169 return; 170 } 171 } 172 } 173 catch(IOException ioe){} 174 } 175 176 177 /** 178 * adds a listener to the factory that will be informed when a new engine will be loaded. 179 * @param listener 180 */ 181 private void addListener(EngineChangeListener listener) { 182 if(!listeners.contains(listener)) { 183 listeners.add(listener); 184 } 185 } 186 187 private void removeListener(EngineChangeListener listener) { 188 listeners.remove(listener); 189 } 190 191 /** 192 * @return CFML Engine 193 * @throws ServletException 194 */ 195 private CFMLEngine getEngine() throws ServletException { 196 if(engine==null)initEngine(); 197 return engine; 198 } 199 200 private void initEngine() throws ServletException { 201 202 int coreVersion=Version.getIntVersion(); 203 long coreCreated=Version.getCreateTime(); 204 205 206 // get newest railo version as file 207 File patcheDir=null; 208 try { 209 patcheDir = getPatchDirectory(); 210 log("railo-server-root:"+patcheDir.getParent()); 211 } 212 catch (IOException e) { 213 throw new ServletException(e); 214 } 215 216 File[] patches=PATCH_ENABLED?patcheDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})):null; 217 File railo=null; 218 if(patches!=null) { 219 for(int i=0;i<patches.length;i++) { 220 if(patches[i].getName().startsWith("tmp.rc")) { 221 patches[i].delete(); 222 } 223 else if(patches[i].lastModified()<coreCreated) { 224 patches[i].delete(); 225 } 226 else if(railo==null || isNewerThan(Util.toInVersion(patches[i].getName()),Util.toInVersion(railo.getName()))) { 227 railo=patches[i]; 228 } 229 } 230 } 231 if(railo!=null && isNewerThan(coreVersion,Util.toInVersion(railo.getName())))railo=null; 232 233 // Load Railo 234 //URL url=null; 235 try { 236 // Load core version when no patch available 237 if(railo==null) { 238 tlog("Load Build in Core"); 239 // 240 String coreExt=getCoreExtension(); 241 engine=getCore(coreExt); 242 243 244 railo=new File(patcheDir,engine.getVersion()+"."+coreExt); 245 if(PATCH_ENABLED) { 246 InputStream bis = new TP().getClass().getResourceAsStream("/core/core."+coreExt); 247 OutputStream bos=new BufferedOutputStream(new FileOutputStream(railo)); 248 Util.copy(bis,bos); 249 Util.closeEL(bis,bos); 250 } 251 } 252 else { 253 try { 254 engine=getEngine(new RailoClassLoader(railo,mainClassLoader)); 255 } 256 catch(EOFException e) { 257 System.err.println("Railo patch file "+railo+" is invalid, please delete it"); 258 engine=getCore(getCoreExtension()); 259 } 260 } 261 version=Util.toInVersion(engine.getVersion()); 262 263 tlog("Loaded Railo Version "+engine.getVersion()); 264 } 265 catch(InvocationTargetException e) { 266 e.getTargetException().printStackTrace(); 267 throw new ServletException(e.getTargetException()); 268 } 269 catch(Exception e) { 270 e.printStackTrace(); 271 throw new ServletException(e); 272 } 273 274 //check updates 275 String updateType=engine.getUpdateType(); 276 if(updateType==null || updateType.length()==0)updateType="manuell"; 277 278 if(updateType.equalsIgnoreCase("auto")) { 279 new UpdateChecker(this).start(); 280 } 281 282 } 283 284 285 private String getCoreExtension() throws ServletException { 286 URL res = new TP().getClass().getResource("/core/core.rcs"); 287 if(res!=null) return "rcs"; 288 289 res = new TP().getClass().getResource("/core/core.rc"); 290 if(res!=null) return "rc"; 291 292 throw new ServletException("missing core file"); 293 } 294 295 private CFMLEngine getCore(String ext) throws SecurityException, IllegalArgumentException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException { 296 InputStream is = null; 297 try { 298 is = new TP().getClass().getResourceAsStream("/core/core."+ext); 299 RailoClassLoader classLoader=new RailoClassLoader(is,mainClassLoader,ext.equalsIgnoreCase("rcs")); 300 return getEngine(classLoader); 301 } 302 finally { 303 Util.closeEL(is); 304 } 305 } 306 307 /** 308 * method to initalize a update of the CFML Engine. 309 * checks if there is a new Version and update it whwn a new version is available 310 * @param password 311 * @return has updated 312 * @throws IOException 313 * @throws ServletException 314 */ 315 public boolean update(String password) throws IOException, ServletException { 316 if(!engine.can(CFMLEngine.CAN_UPDATE,password)) 317 throw new IOException("access denied to update CFMLEngine"); 318 //new RunUpdate(this).start(); 319 return update(); 320 } 321 322 /** 323 * restart the cfml engine 324 * @param password 325 * @return has updated 326 * @throws IOException 327 * @throws ServletException 328 */ 329 public boolean restart(String password) throws IOException, ServletException { 330 if(!engine.can(CFMLEngine.CAN_RESTART_ALL,password)) 331 throw new IOException("access denied to restart CFMLEngine"); 332 333 return _restart(); 334 } 335 336 /** 337 * restart the cfml engine 338 * @param password 339 * @return has updated 340 * @throws IOException 341 * @throws ServletException 342 */ 343 public boolean restart(String configId, String password) throws IOException, ServletException { 344 if(!engine.can(CFMLEngine.CAN_RESTART_CONTEXT,password))// TODO restart single context 345 throw new IOException("access denied to restart CFML Context (configId:"+configId+")"); 346 347 return _restart(); 348 } 349 350 /** 351 * restart the cfml engine 352 * @param password 353 * @return has updated 354 * @throws IOException 355 * @throws ServletException 356 */ 357 private synchronized boolean _restart() throws ServletException { 358 engine.reset(); 359 initEngine(); 360 registerInstance(engine); 361 callListeners(engine); 362 System.gc(); 363 System.gc(); 364 return true; 365 } 366 367 /** 368 * updates the engine when a update is available 369 * @return has updated 370 * @throws IOException 371 * @throws ServletException 372 */ 373 private boolean update() throws IOException, ServletException { 374 375 URL hostUrl=getEngine().getUpdateLocation(); 376 if(hostUrl==null)hostUrl=new URL("http://www.getrailo.org"); 377 URL infoUrl=new URL(hostUrl,"/railo/remote/version/info.cfm?ext="+getCoreExtension()+"&version="+version);// FUTURE replace with Info.cfc or better move the functionality to core if possible. something like engine.getUpdater a class provided by the core and defined (interface) by the loader. 378 379 tlog("Check for update at "+hostUrl); 380 381 String availableVersion = Util.toString((InputStream)infoUrl.getContent()).trim(); 382 383 if(availableVersion.length()!=9) throw new IOException("can't get update info from ["+infoUrl+"]"); 384 if(!isNewerThan(Util.toInVersion(availableVersion),version)) { 385 tlog("There is no newer Version available"); 386 return false; 387 } 388 389 tlog("Found a newer Version \n - current Version "+Util.toStringVersion(version)+"\n - available Version "+availableVersion); 390 391 URL updateUrl=new URL(hostUrl,"/railo/remote/version/update.cfm?ext="+getCoreExtension()+"&version="+availableVersion); 392 File patchDir=getPatchDirectory(); 393 File newRailo=new File(patchDir,availableVersion+("."+getCoreExtension()));//isSecure?".rcs":".rc" 394 395 if(newRailo.createNewFile()) { 396 Util.copy((InputStream)updateUrl.getContent(),new FileOutputStream(newRailo)); 397 } 398 else { 399 tlog("File for new Version already exists, won't copy new one"); 400 return false; 401 } 402 try { 403 engine.reset(); 404 } 405 catch(Throwable t) { 406 t.printStackTrace(); 407 } 408 409 // Test new railo version valid 410 //FileClassLoader classLoader=new FileClassLoader(newRailo,mainClassLoader); 411 RailoClassLoader classLoader=new RailoClassLoader(newRailo,mainClassLoader); 412 //URLClassLoader classLoader=new URLClassLoader(new URL[]{newRailo.toURL()},mainClassLoader); 413 String v=""; 414 try { 415 CFMLEngine e = getEngine(classLoader); 416 if(e==null)throw new IOException("can't load engine"); 417 v=e.getVersion(); 418 engine=e; 419 version=Util.toInVersion(v); 420 //e.reset(); 421 callListeners(e); 422 } 423 catch (Exception e) { 424 classLoader=null; 425 System.gc(); 426 try { 427 newRailo.delete(); 428 } 429 catch(Exception ee){} 430 tlog("There was a Problem with the new Version, can't install ("+e+":"+e.getMessage()+")"); 431 e.printStackTrace(); 432 return false; 433 } 434 435 tlog("Version ("+v+")installed"); 436 return true; 437 } 438 439 440 /** 441 * method to initalize a update of the CFML Engine. 442 * checks if there is a new Version and update it whwn a new version is available 443 * @param password 444 * @return has updated 445 * @throws IOException 446 * @throws ServletException 447 */ 448 public boolean removeUpdate(String password) throws IOException, ServletException { 449 if(!engine.can(CFMLEngine.CAN_UPDATE,password)) 450 throw new IOException("access denied to update CFMLEngine"); 451 return removeUpdate(); 452 } 453 454 455 /** 456 * method to initalize a update of the CFML Engine. 457 * checks if there is a new Version and update it whwn a new version is available 458 * @param password 459 * @return has updated 460 * @throws IOException 461 * @throws ServletException 462 */ 463 public boolean removeLatestUpdate(String password) throws IOException, ServletException { 464 if(!engine.can(CFMLEngine.CAN_UPDATE,password)) 465 throw new IOException("access denied to update CFMLEngine"); 466 return removeLatestUpdate(); 467 } 468 469 470 471 /** 472 * updates the engine when a update is available 473 * @return has updated 474 * @throws IOException 475 * @throws ServletException 476 */ 477 private boolean removeUpdate() throws IOException, ServletException { 478 File patchDir=getPatchDirectory(); 479 File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"railo","rc","rcs"})); 480 481 for(int i=0;i<patches.length;i++) { 482 if(!patches[i].delete())patches[i].deleteOnExit(); 483 } 484 _restart(); 485 return true; 486 } 487 488 489 private boolean removeLatestUpdate() throws IOException, ServletException { 490 File patchDir=getPatchDirectory(); 491 File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); 492 File patch=null; 493 for(int i=0;i<patches.length;i++) { 494 if(patch==null || isNewerThan(Util.toInVersion(patches[i].getName()),Util.toInVersion(patch.getName()))) { 495 patch=patches[i]; 496 } 497 } 498 if(patch!=null && !patch.delete())patch.deleteOnExit(); 499 500 _restart(); 501 return true; 502 } 503 504 505 public String[] getInstalledPatches() throws ServletException, IOException { 506 File patchDir=getPatchDirectory(); 507 File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); 508 509 List<String> list=new ArrayList<String>(); 510 String name; 511 int extLen=getCoreExtension().length()+1; 512 for(int i=0;i<patches.length;i++) { 513 name=patches[i].getName(); 514 name=name.substring(0, name.length()-extLen); 515 list.add(name); 516 } 517 String[] arr = list.toArray(new String[list.size()]); 518 Arrays.sort(arr); 519 return arr; 520 } 521 522 523 /** 524 * call all registred listener for update of the engine 525 * @param engine 526 */ 527 private void callListeners(CFMLEngine engine) { 528 Iterator<EngineChangeListener> it = listeners.iterator(); 529 while(it.hasNext()) { 530 it.next().onUpdate(engine); 531 } 532 } 533 534 535 private File getPatchDirectory() throws IOException { 536 File pd = new File(getResourceRoot(),"patches"); 537 if(!pd.exists())pd.mkdirs(); 538 return pd; 539 } 540 541 /** 542 * return directory to railo resource root 543 * @return railo root directory 544 * @throws IOException 545 */ 546 public File getResourceRoot() throws IOException { 547 if(resourceRoot==null) { 548 resourceRoot=new File(getRuningContextRoot(),"railo-server"); 549 if(!resourceRoot.exists()) resourceRoot.mkdirs(); 550 } 551 return resourceRoot; 552 } 553 554 /** 555 * @return return running context root 556 * @throws IOException 557 * @throws IOException 558 */ 559 private File getRuningContextRoot() throws IOException { 560 561 if(railoServerRoot!=null) { 562 return railoServerRoot; 563 } 564 File dir=getClassLoaderRoot(mainClassLoader); 565 dir.mkdirs(); 566 if(dir.exists() && dir.isDirectory()) return dir; 567 568 569 570 throw new IOException("can't create/write to directory ["+dir+"], set \"init-param\" \"railo-server-directory\" with path to writable directory"); 571 } 572 /** 573 * returns the path where the classloader is located 574 * @param cl ClassLoader 575 * @return file of the classloader root 576 */ 577 public static File getClassLoaderRoot(ClassLoader cl) { 578 String path="railo/loader/engine/CFMLEngine.class"; 579 URL res = cl.getResource(path); 580 581 // get file and remove all after ! 582 String strFile=null; 583 try { 584 strFile = URLDecoder.decode(res.getFile().trim(),"iso-8859-1"); 585 } catch (UnsupportedEncodingException e) { 586 587 } 588 int index=strFile.indexOf('!'); 589 if(index!=-1)strFile=strFile.substring(0,index); 590 591 // remove path at the end 592 index=strFile.lastIndexOf(path); 593 if(index!=-1)strFile=strFile.substring(0,index); 594 595 // remove "file:" at start and railo.jar at the end 596 if(strFile.startsWith("file:"))strFile=strFile.substring(5); 597 if(strFile.endsWith("railo.jar")) strFile=strFile.substring(0,strFile.length()-9); 598 599 File file=new File(strFile); 600 if(file.isFile())file=file.getParentFile(); 601 602 return file; 603 } 604 605 /** 606 * check left value against right value 607 * @param left 608 * @param right 609 * @return returns if right is newer than left 610 */ 611 private boolean isNewerThan(int left, int right) { 612 return left>right; 613 } 614 615 /** 616 * Load CFMl Engine Implementation (railo.runtime.engine.CFMLEngineImpl) from a Classloader 617 * @param classLoader 618 * @return loaded CFML Engine 619 * @throws ClassNotFoundException 620 * @throws NoSuchMethodException 621 * @throws SecurityException 622 * @throws InvocationTargetException 623 * @throws IllegalAccessException 624 * @throws IllegalArgumentException 625 */ 626 private CFMLEngine getEngine(ClassLoader classLoader) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { 627 Class clazz=classLoader.loadClass("railo.runtime.engine.CFMLEngineImpl"); 628 Method m = clazz.getMethod("getInstance",new Class[]{CFMLEngineFactory.class}); 629 return (CFMLEngine) m.invoke(null,new Object[]{this}); 630 631 } 632 633 /** 634 * log info to output 635 * @param obj Object to output 636 */ 637 public void tlog(Object obj) { 638 log(new Date()+ " "+obj); 639 } 640 641 /** 642 * log info to output 643 * @param obj Object to output 644 */ 645 public void log(Object obj) { 646 if(out==null){ 647 boolean isCLI=false; 648 String str=System.getProperty("railo.cli.call"); 649 if(!Util.isEmpty(str, true)) { 650 str=str.trim(); 651 isCLI="true".equalsIgnoreCase(str) || "yes".equalsIgnoreCase(str); 652 653 } 654 655 if(isCLI) { 656 try{ 657 File dir = new File(getResourceRoot(),"logs"); 658 dir.mkdirs(); 659 File file = new File(dir,"out"); 660 661 file.createNewFile(); 662 out=new PrintWriter(file); 663 } 664 catch(Throwable t){t.printStackTrace();} 665 } 666 if(out==null)out=new PrintWriter(System.out); 667 } 668 out.write(""+obj+"\n"); 669 out.flush(); 670 } 671 672 private class UpdateChecker extends Thread { 673 private CFMLEngineFactory factory; 674 675 private UpdateChecker(CFMLEngineFactory factory) { 676 this.factory=factory; 677 } 678 679 public void run() { 680 long time=10000; 681 while(true) { 682 try { 683 sleep(time); 684 time=1000*60*60*24; 685 factory.update(); 686 687 } catch (Exception e) { 688 689 } 690 } 691 } 692 } 693 694 }