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    }