001    package railo.runtime.config;
002    
003    import java.io.ByteArrayInputStream;
004    import java.io.ByteArrayOutputStream;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.util.ArrayList;
008    import java.util.List;
009    import java.util.jar.Attributes;
010    import java.util.jar.Manifest;
011    import java.util.zip.ZipEntry;
012    import java.util.zip.ZipException;
013    import java.util.zip.ZipFile;
014    import java.util.zip.ZipInputStream;
015    
016    import railo.print;
017    import railo.commons.io.IOUtil;
018    import railo.commons.io.SystemUtil;
019    import railo.commons.io.compress.ZipUtil;
020    import railo.commons.io.log.LogAndSource;
021    import railo.commons.io.res.Resource;
022    import railo.commons.io.res.filter.ExtensionResourceFilter;
023    import railo.commons.io.res.filter.ResourceFilter;
024    import railo.commons.io.res.util.FileWrapper;
025    import railo.commons.io.res.util.ResourceUtil;
026    import railo.commons.lang.ExceptionUtil;
027    import railo.commons.lang.StringUtil;
028    import railo.commons.lang.SystemOut;
029    import railo.runtime.Info;
030    import railo.runtime.config.Config;
031    import railo.runtime.extension.RHExtension;
032    import railo.runtime.op.Caster;
033    import railo.runtime.op.Decision;
034    import railo.runtime.type.util.ListUtil;
035    
036    public class DeployHandler {
037            
038            
039    
040            private static final ResourceFilter ALL_EXT = new ExtensionResourceFilter(new String[]{".re",".ra",".ras"});
041            //private static final ResourceFilter ARCHIVE_EXT = new ExtensionResourceFilter(new String[]{".ra",".ras"});
042    
043    
044            public static void deploy(Config config){
045                    synchronized (config) {
046                            Resource dir = getDeployDirectory(config);
047                            int ma = Info.getMajorVersion();
048                            int mi = Info.getMinorVersion();
049                            if(!dir.exists()) {
050                                    if(ma>4 || ma==4 && mi>1) {// FUTURE remove the if contition
051                                            dir.mkdirs();
052                                    }
053                                    return;
054                            }
055                            
056                            Resource[] children = dir.listResources(ALL_EXT);
057                            Resource child;
058                            String ext;
059                            for(int i=0;i<children.length;i++){
060                                    child=children[i];
061                                    try {
062                                    // Railo archives
063                                    ext=ResourceUtil.getExtension(child, null);
064                                    if("ra".equalsIgnoreCase(ext) || "ras".equalsIgnoreCase(ext)) {
065                                            deployArchive(config,child);
066                                    }
067                                    
068                                    // Railo Extensions
069                                    else if("re".equalsIgnoreCase(ext))
070                                            deployExtension(config, child);
071                                    }
072                                    catch (ZipException e) {
073                                            SystemOut.printDate(config.getErrWriter(),ExceptionUtil.getStacktrace(e, true));
074                                    }
075                                    catch (IOException e) {
076                                            SystemOut.printDate(config.getErrWriter(),ExceptionUtil.getStacktrace(e, true));
077                                    }
078                            }
079                    }
080            }
081    
082            public static Resource getDeployDirectory(Config config) {
083                    return config.getConfigDir().getRealResource("deploy");
084            }
085    
086            private static void deployArchive(Config config,Resource archive) throws ZipException, IOException {
087                    LogAndSource log = ((ConfigImpl)config).getDeployLogger();
088                    String type=null,virtual=null,name=null;
089                    boolean readOnly,topLevel,hidden,physicalFirst;
090                    short inspect;
091                    InputStream is = null;
092                    ZipFile file=null;
093                    try {
094                    file=new ZipFile(FileWrapper.toFile(archive));
095                    ZipEntry entry = file.getEntry("META-INF/MANIFEST.MF");
096                    
097                    // no manifest
098                    if(entry==null) {
099                            log.error("archive","cannot deploy Railo Archive ["+archive+"], file is to old, the file does not have a MANIFEST.");
100                            moveToFailedFolder(archive);
101                            return;
102                    }
103                    
104                            is = file.getInputStream(entry);
105                            Manifest manifest = new Manifest(is);
106                        Attributes attr = manifest.getMainAttributes();
107                        
108                        //id = unwrap(attr.getValue("mapping-id"));
109                        type = unwrap(attr.getValue("mapping-type"));
110                        virtual = unwrap(attr.getValue("mapping-virtual-path"));
111                        name = ListUtil.trim(virtual, "/");
112                        readOnly = Caster.toBooleanValue(unwrap(attr.getValue("mapping-readonly")),false);
113                        topLevel = Caster.toBooleanValue(unwrap(attr.getValue("mapping-top-level")),false);
114                        inspect = ConfigWebUtil.inspectTemplate(unwrap(attr.getValue("mapping-inspect")), ConfigImpl.INSPECT_UNDEFINED);
115                        if(inspect==ConfigImpl.INSPECT_UNDEFINED) {
116                            Boolean trusted = Caster.toBoolean(unwrap(attr.getValue("mapping-trusted")),null);
117                            if(trusted!=null) {
118                                    if(trusted.booleanValue()) inspect=ConfigImpl.INSPECT_NEVER;
119                                    else inspect=ConfigImpl.INSPECT_ALWAYS;
120                            }       
121                        }
122                        hidden = Caster.toBooleanValue(unwrap(attr.getValue("mapping-hidden")),false);
123                        physicalFirst = Caster.toBooleanValue(unwrap(attr.getValue("mapping-physical-first")),false);
124                    }
125                    finally{
126                            IOUtil.closeEL(is);
127                            ZipUtil.close(file);
128                    }
129                    Resource trgDir = config.getConfigDir().getRealResource("archives").getRealResource(type).getRealResource(name);
130                    Resource trgFile = trgDir.getRealResource(archive.getName());
131                    trgDir.mkdirs();
132                    
133                    // delete existing files
134                    
135                    try {
136                            ResourceUtil.deleteContent(trgDir, null);
137                            ResourceUtil.moveTo(archive, trgFile);
138                            
139                            log.info("archive","add "+type+" mapping ["+virtual+"] with archive ["+trgFile.getAbsolutePath()+"]");
140                            if("regular".equalsIgnoreCase(type))
141                                    ConfigWebAdmin.updateMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect, topLevel);
142                            else if("cfc".equalsIgnoreCase(type))
143                                    ConfigWebAdmin.updateComponentMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect);
144                            else if("ct".equalsIgnoreCase(type))
145                                    ConfigWebAdmin.updateCustomTagMapping((ConfigImpl)config,virtual, null, trgFile.getAbsolutePath(), "archive", inspect);
146                            
147                        
148                    }
149                    catch (Throwable t) {
150                            moveToFailedFolder(archive);
151                            log.error("archive",ExceptionUtil.getStacktrace(t, true));
152                    }
153            }
154            
155    
156            private static void deployExtension(Config config, Resource ext) {
157                    ConfigImpl ci = (ConfigImpl)config;
158                    boolean isWeb=config instanceof ConfigWeb;
159                    String type=isWeb?"web":"server";
160                    LogAndSource log = ((ConfigImpl)config).getDeployLogger();
161                    
162                    // Manifest
163                    Manifest manifest = null;
164                    ZipInputStream zis=null;
165            try {
166                    zis = new ZipInputStream( IOUtil.toBufferedInputStream(ext.getInputStream()) ) ;     
167                    ZipEntry entry;
168                    String name;
169                    while ( ( entry = zis.getNextEntry()) != null ) {
170                            name=entry.getName();
171                            if(!entry.isDirectory() && name.equalsIgnoreCase("META-INF/MANIFEST.MF")) {
172                                    manifest = toManifest(config,zis,null);
173                            }
174                        zis.closeEntry() ;
175                    }
176            }
177            catch(Throwable t){
178                    log.error("extension", ExceptionUtil.getStacktrace(t, true));
179                            moveToFailedFolder(ext);
180                            return;
181            }
182            finally {
183                    IOUtil.closeEL(zis);
184            }
185            
186            int minCoreVersion=0;
187            double minLoaderVersion=0;
188            String strMinCoreVersion="",strMinLoaderVersion="",version=null,name=null,id=null;
189            
190            if(manifest!=null) {
191                    Attributes attr = manifest.getMainAttributes();
192                    // version
193                    version=unwrap(attr.getValue("version"));
194    
195    
196                    // id
197                    id=unwrap(attr.getValue("id"));
198                    
199                    // name
200                    name=unwrap(attr.getValue("name"));
201    
202                    // core version
203                    strMinCoreVersion=unwrap(attr.getValue("railo-core-version"));
204                    minCoreVersion=Info.toIntVersion(strMinCoreVersion,minCoreVersion);
205                    
206                    // loader version
207                    strMinLoaderVersion=unwrap(attr.getValue("railo-loader-version"));
208                    minLoaderVersion=Caster.toDoubleValue(strMinLoaderVersion,minLoaderVersion);
209                    
210            }
211            if(StringUtil.isEmpty(name,true)) {
212                    name=ext.getName();
213                    int index=name.lastIndexOf('.');
214                    name=name.substring(0,index-1);
215            }
216            name=name.trim();
217            
218            
219            // check core version
220                    if(minCoreVersion>Info.getVersionAsInt()) {
221                            log.error("extension", "cannot deploy Railo Extension ["+ext+"], Railo Version must be at least ["+strMinCoreVersion+"].");
222                            moveToFailedFolder(ext);
223                            return;
224                    }
225    
226                    // check loader version
227                    if(minLoaderVersion>SystemUtil.getLoaderVersion()) {
228                            log.error("extension", "cannot deploy Railo Extension ["+ext+"], Railo Loader Version must be at least ["+strMinLoaderVersion+"], update the railo.jar first.");
229                            moveToFailedFolder(ext);
230                            return;
231                    }
232                    // check id
233                    if(!Decision.isUUId(id)) {
234                            log.error("extension", "cannot deploy Railo Extension ["+ext+"], this Extension has no valid id ["+id+"],id must be a valid UUID.");
235                            moveToFailedFolder(ext);
236                            return;
237                    }
238                    
239                    
240                    
241                    
242                    Resource trgFile=null;
243                    try{
244                            ConfigWebAdmin.removeRHExtension(ci,id);
245                            
246                            Resource trgDir = config.getConfigDir().getRealResource("extensions").getRealResource(type).getRealResource(name);
247                            trgFile = trgDir.getRealResource(ext.getName());
248                            trgDir.mkdirs();
249                            ResourceUtil.moveTo(ext, trgFile);
250                    }
251                catch(Throwable t){
252                    log.error("extension", ExceptionUtil.getStacktrace(t, true));
253                            moveToFailedFolder(ext);
254                            return;
255                }
256                
257                    try {
258                    zis = new ZipInputStream( IOUtil.toBufferedInputStream(trgFile.getInputStream()) ) ;     
259                    ZipEntry entry;
260                    String path;
261                    String fileName;
262                    List<String> jars=new ArrayList<String>(), flds=new ArrayList<String>(), tlds=new ArrayList<String>(), contexts=new ArrayList<String>(), applications=new ArrayList<String>();
263                    while ( ( entry = zis.getNextEntry()) != null ) {
264                            path=entry.getName();
265                            fileName=fileName(entry);
266                            // jars
267                            if(!entry.isDirectory() && (startsWith(path,type,"jars") || startsWith(path,type,"jar") || startsWith(path,type,"lib") || startsWith(path,type,"libs")) && StringUtil.endsWithIgnoreCase(path, ".jar")) {
268                                    log.info("extension","deploy jar "+fileName);
269                                    ConfigWebAdmin.updateJar(config,zis,fileName,false);
270                                    jars.add(fileName);
271                            }
272                            
273                            // flds
274                            if(!entry.isDirectory() && startsWith(path,type,"flds") && StringUtil.endsWithIgnoreCase(path, ".fld")) {
275                                    log.info("extension","deploy fld "+fileName);
276                                    ConfigWebAdmin.updateFLD(config, zis, fileName,false);
277                                    flds.add(fileName);
278                            }
279                            
280                            // tlds
281                            if(!entry.isDirectory() && startsWith(path,type,"tlds") && StringUtil.endsWithIgnoreCase(path, ".tld")) {
282                                    log.info("extension","deploy tld "+fileName);
283                                    ConfigWebAdmin.updateTLD(config, zis, fileName,false); 
284                                    tlds.add(fileName);
285                            }
286                            
287                            // context
288                            String realpath;
289                            if(!entry.isDirectory() && startsWith(path,type,"context") && !StringUtil.startsWith(fileName(entry), '.')) {
290                                    realpath=path.substring(8);
291                                    //log.info("extension","deploy context "+realpath);
292                                    log.info("extension","deploy context "+realpath);
293                                    ConfigWebAdmin.updateContext(ci, zis, realpath,false);
294                                    contexts.add(realpath);
295                            }
296                            
297                            // applications
298                            if(!entry.isDirectory() && startsWith(path,type,"applications") && !StringUtil.startsWith(fileName(entry), '.')) {
299                                    realpath=path.substring(13);
300                                    //log.info("extension","deploy context "+realpath);
301                                    log.info("extension","deploy application "+realpath);
302                                    ConfigWebAdmin.updateApplication(ci, zis, realpath,false);
303                                    applications.add(realpath);
304                            }
305                            
306                            
307                        zis.closeEntry() ;
308                    }
309                    
310                    //installation successfull
311                    
312                    ConfigWebAdmin.updateRHExtension(ci,
313                                    new RHExtension(id,name,version,
314                                    jars.toArray(new String[jars.size()]),
315                                    flds.toArray(new String[flds.size()]),
316                                    tlds.toArray(new String[tlds.size()]),
317                                    contexts.toArray(new String[contexts.size()]),
318                                    applications.toArray(new String[applications.size()])));
319                    
320            }
321                catch(Throwable t){
322                    // installation failed
323                    
324                    log.error("extension",ExceptionUtil.getStacktrace(t, true));
325                            moveToFailedFolder(trgFile);
326                            return;
327                }
328            finally {
329                    IOUtil.closeEL(zis);
330            }
331            }
332    
333            private static Manifest toManifest(Config config,InputStream is, Manifest defaultValue) {
334                    try {
335                            String cs = config.getResourceCharset();
336                            String str = IOUtil.toString(is,cs);
337                            if(StringUtil.isEmpty(str,true)) return defaultValue;
338                            str=str.trim()+"\n";
339                            return new Manifest(new ByteArrayInputStream(str.getBytes(cs)));
340                    }
341                    catch (Throwable t) {
342                            return defaultValue;
343                    }
344            }
345    
346            private static boolean startsWith(String path,String type, String name) {
347                    return StringUtil.startsWithIgnoreCase(path, name+"/") || StringUtil.startsWithIgnoreCase(path, type+"/"+name+"/");
348            }
349    
350            private static String fileName(ZipEntry entry) {
351                    String name = entry.getName();
352                    int index=name.lastIndexOf('/');
353                    if(index==-1) return name;
354                    return name.substring(index+1);
355            }
356    
357            private static void moveToFailedFolder(Resource archive) {
358                    Resource dir = archive.getParentResource().getRealResource("failed-to-deploy");
359                    Resource dst = dir.getRealResource(archive.getName());
360                    dir.mkdirs();
361                    
362                    try {
363                            if(dst.exists()) dst.remove(true);
364                            ResourceUtil.moveTo(archive, dst);
365                    }
366                    catch (Throwable t) {}
367                    
368                    // TODO Auto-generated method stub
369                    
370            }
371    
372            private static String unwrap(String value) {
373                    if(value==null) return "";
374                    String res = unwrap(value,'"');
375                    if(res!=null) return res; // was double quote
376                    
377                    return unwrap(value,'\''); // try single quote unwrap, when there is no double quote.
378            }
379            
380            private static String unwrap(String value, char del) {
381                    value=value.trim();
382                    if(StringUtil.startsWith(value, del) && StringUtil.endsWith(value, del)) {
383                            return value.substring(1, value.length()-1);
384                    }
385                    return value;
386            }
387            
388    }