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