001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.io.res.util;
020
021import java.io.Closeable;
022import java.io.IOException;
023import java.net.URL;
024import java.net.URLClassLoader;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.List;
028import java.util.Map;
029
030import lucee.commons.digest.MD5;
031import lucee.commons.io.res.Resource;
032import lucee.commons.io.res.type.file.FileResource;
033import lucee.runtime.exp.PageException;
034import lucee.runtime.type.util.ArrayUtil;
035
036import org.apache.commons.collections.map.ReferenceMap;
037
038/**
039 * Classloader that load classes from resources
040 */
041public final class ResourceClassLoader extends URLClassLoader implements Closeable {
042
043        private List<Resource> resources=new ArrayList<Resource>();
044        private Map<String,ResourceClassLoader> customCLs; 
045        
046        /* *
047         * Constructor of the class
048         * @param resources
049         * @throws PageException
050         
051        ResourceClassLoader(Resource[] resources) throws IOException {
052                super(doURLs(resources));
053        }*/
054        
055        /**
056         * Constructor of the class
057         * @param reses
058         * @param parent
059         * @throws PageException
060         */
061        public ResourceClassLoader(Resource[] resources, ClassLoader parent) throws IOException {
062                super(doURLs(resources), parent);
063                for(int i=0;i<resources.length;i++){
064                        this.resources.add(resources[i]);
065                }
066        }
067        
068        public ResourceClassLoader(ClassLoader parent) {
069                super(new URL[0], parent);
070        }
071
072        /**
073         * @return the resources
074         */
075        public Resource[] getResources() {
076                return resources.toArray(new Resource[resources.size()]);
077        }
078
079        /**
080         * translate resources to url Objects
081         * @param reses
082         * @return
083         * @throws PageException
084         */
085        public static URL[] doURLs(Resource[] reses) throws IOException {
086                List<URL> list=new ArrayList<URL>();
087                for(int i=0;i<reses.length;i++) {
088                        if(reses[i].isDirectory() || "jar".equalsIgnoreCase(ResourceUtil.getExtension(reses[i],null)))
089                                list.add(doURL(reses[i]));
090                }
091                return list.toArray(new URL[list.size()]);
092        
093        }
094        private static URL doURL(Resource res) throws IOException {
095                        if(!(res instanceof FileResource))
096                                throw new IOException("resource ["+res.getPath()+"] must be a local file");
097                        return ((FileResource)res).toURL();
098        }
099        
100        @Override
101        public void close(){}
102
103        public synchronized void addResourcesX(Resource[] reses) throws IOException {
104                for(int i=0;i<reses.length;i++){
105                        if(!this.resources.contains(reses[i])){
106                                this.resources.add(reses[i]);
107                                addURL(doURL(reses[i]));
108                        }
109                }
110        }
111        
112
113        public ResourceClassLoader getCustomResourceClassLoader(Resource[] resources) throws IOException{
114                if(ArrayUtil.isEmpty(resources)) return this;
115                String key = hash(resources);
116                ResourceClassLoader rcl=customCLs==null?null:customCLs.get(key);
117                if(rcl!=null) return rcl; 
118                
119                resources=ResourceUtil.merge(this.getResources(), resources);
120                rcl=new ResourceClassLoader(resources,getParent());
121                if(customCLs==null)customCLs=new ReferenceMap();
122                customCLs.put(key, rcl);
123                return rcl;
124        }
125
126        public ResourceClassLoader getCustomResourceClassLoader2(Resource[] resources) throws IOException{
127                if(ArrayUtil.isEmpty(resources)) return this;
128                String key = hash(resources);
129                ResourceClassLoader rcl=customCLs==null?null:customCLs.get(key);
130                if(rcl!=null) return rcl; 
131                
132                rcl=new ResourceClassLoader(resources,this);
133                if(customCLs==null)customCLs=new ReferenceMap();
134                customCLs.put(key, rcl);
135                return rcl;
136        }
137        
138        private String hash(Resource[] resources) {
139                Arrays.sort(resources);
140                StringBuilder sb=new StringBuilder();
141                for(int i=0;i<resources.length;i++){
142                        sb.append(ResourceUtil.getCanonicalPathEL(resources[i]));
143                        sb.append(';');
144                }
145                return MD5.getDigestAsString(sb.toString(),null);
146        }
147
148}