001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.common.xbean;
018    
019    import java.io.File;
020    import java.io.FilenameFilter;
021    import java.net.MalformedURLException;
022    import java.net.URL;
023    import java.util.ArrayList;
024    import java.util.List;
025    import java.util.ListIterator;
026    
027    import javax.xml.parsers.DocumentBuilder;
028    
029    import org.apache.servicemix.jbi.container.JBIContainer;
030    import org.apache.servicemix.jbi.framework.SharedLibrary;
031    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
032    import org.apache.xbean.classloader.JarFileClassLoader;
033    import org.apache.xbean.server.repository.FileSystemRepository;
034    import org.apache.xbean.server.repository.Repository;
035    import org.apache.xbean.server.spring.loader.SpringLoader;
036    import org.apache.xbean.spring.context.SpringApplicationContext;
037    import org.apache.xbean.spring.context.SpringXmlPreprocessor;
038    import org.springframework.beans.FatalBeanException;
039    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
040    import org.w3c.dom.Document;
041    import org.w3c.dom.Element;
042    import org.w3c.dom.NodeList;
043    import org.w3c.dom.Text;
044    
045    /**
046     * An advanced xml preprocessor that will create a default classloader for the SU if none
047     * is configured.
048     * 
049     * @author gnodet
050     */
051    public class ClassLoaderXmlPreprocessor implements SpringXmlPreprocessor {
052    
053        public static final String CLASSPATH_XML = "classpath.xml";
054        public static final String LIB_DIR = "/lib";
055        
056        private final FileSystemRepository repository;
057        private final JBIContainer container;
058        
059        public ClassLoaderXmlPreprocessor(Repository repository) {
060            this(repository, null);
061        }
062    
063        public ClassLoaderXmlPreprocessor(Repository repository, JBIContainer container) {
064            if (repository instanceof FileSystemRepository == false) {
065                throw new IllegalArgumentException("repository must be a FileSystemRepository");
066            }
067            this.repository = (FileSystemRepository) repository;
068            this.container = container;
069        }
070    
071        public void preprocess(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
072            // determine the classLoader
073            ClassLoader classLoader;
074            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
075            if (classpathElements.getLength() == 0) {
076                // Check if a classpath.xml file exists in the root of the SU
077                URL url = repository.getResource(CLASSPATH_XML);
078                if (url != null) {
079                    try {
080                        DocumentBuilder builder = new SourceTransformer().createDocumentBuilder();
081                        Document doc = builder.parse(url.toString());
082                        classLoader = getClassLoader(applicationContext, reader, doc);
083                    } catch (Exception e) {
084                        throw new FatalBeanException("Unable to load classpath.xml file", e);
085                    }
086                } else {
087                    try {
088                        URL[] urls = getDefaultLocations();
089                        ClassLoader parentLoader = getParentClassLoader(applicationContext);
090                        classLoader = new JarFileClassLoader(applicationContext.getDisplayName(), urls, parentLoader);
091                        // assign the class loader to the xml reader and the
092                        // application context
093                    } catch (Exception e) {
094                        throw new FatalBeanException("Unable to create default classloader for SU", e);
095                    }
096                }
097            } else {
098                classLoader = getClassLoader(applicationContext, reader, document);
099            }
100            reader.setBeanClassLoader(classLoader);
101            applicationContext.setClassLoader(classLoader);
102            Thread.currentThread().setContextClassLoader(classLoader);
103        }
104        
105        protected URL[] getDefaultLocations() {
106            try {
107                File root = repository.getRoot();
108                File[] jars = new File(root, LIB_DIR).listFiles(new FilenameFilter() {
109                    public boolean accept(File dir, String name) {
110                        name = name.toLowerCase();
111                        return name.endsWith(".jar") || name.endsWith(".zip");
112                    }
113                });
114                URL[] urls = new URL[jars != null ? jars.length + 1 : 1];
115                urls[0] = root.toURL();
116                if (jars != null) {
117                    for (int i = 0; i < jars.length; i++) {
118                        urls[i+1] = jars[i].toURL();
119                    }
120                }
121                return urls;
122            } catch (MalformedURLException e) {
123                throw new FatalBeanException("Unable to get default classpath locations", e);
124            }
125        }
126        
127        protected ClassLoader getClassLoader(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
128            // determine the classLoader
129            ClassLoader classLoader;
130            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
131            if (classpathElements.getLength() < 1) {
132                classLoader = getParentClassLoader(applicationContext);
133            } else if (classpathElements.getLength() > 1) {
134                throw new FatalBeanException("Expected only classpath element but found " + classpathElements.getLength());
135            } else {
136                Element classpathElement = (Element) classpathElements.item(0);
137                
138                // Delegation mode
139                boolean inverse = false;
140                String inverseAttr = classpathElement.getAttribute("inverse");
141                if (inverseAttr != null && "true".equalsIgnoreCase(inverseAttr)) {
142                    inverse = true;
143                }
144    
145                // build hidden classes
146                List<String> hidden = new ArrayList<String>();
147                NodeList hiddenElems = classpathElement.getElementsByTagName("hidden");
148                for (int i = 0; i < hiddenElems.getLength(); i++) {
149                    Element hiddenElement = (Element) hiddenElems.item(i);
150                    String pattern = ((Text) hiddenElement.getFirstChild()).getData().trim();
151                    hidden.add(pattern);
152                }
153    
154                // build non overridable classes
155                List<String> nonOverridable = new ArrayList<String>();
156                NodeList nonOverridableElems = classpathElement.getElementsByTagName("nonOverridable");
157                for (int i = 0; i < nonOverridableElems.getLength(); i++) {
158                    Element nonOverridableElement = (Element) nonOverridableElems.item(i);
159                    String pattern = ((Text) nonOverridableElement.getFirstChild()).getData().trim();
160                    nonOverridable.add(pattern);
161                }
162    
163                // build the classpath
164                List<String> classpath = new ArrayList<String>();
165                NodeList locations = classpathElement.getElementsByTagName("location");
166                for (int i = 0; i < locations.getLength(); i++) {
167                    Element locationElement = (Element) locations.item(i);
168                    String location = ((Text) locationElement.getFirstChild()).getData().trim();
169                    classpath.add(location);
170                }
171                
172                // Add shared libraries
173                List<String> sls = new ArrayList<String>();
174                NodeList libraries = classpathElement.getElementsByTagName("library");
175                for (int i = 0; i < libraries.getLength(); i++) {
176                    Element locationElement = (Element) libraries.item(i);
177                    String library = ((Text) locationElement.getFirstChild()).getData().trim();
178                    sls.add(library);
179                }
180                if (sls.size() > 0 && container == null) {
181                    throw new IllegalStateException("Can not reference shared libraries if the component is not deployed in ServiceMix");
182                }
183                
184                // convert the paths to URLS
185                URL[] urls;
186                if (classpath.size() != 0) {
187                    urls = new URL[classpath.size()];
188                    for (ListIterator<String> iterator = classpath.listIterator(); iterator.hasNext();) {
189                        String location = iterator.next();
190                        URL url = repository.getResource(location);
191                        if (url == null) {
192                            throw new FatalBeanException("Unable to resolve classpath location " + location);
193                        }
194                        urls[iterator.previousIndex()] = url;
195                    }
196                } else {
197                    urls = getDefaultLocations();
198                }
199    
200                // create the classloader
201                List<ClassLoader> parents = new ArrayList<ClassLoader>();
202                parents.add(getParentClassLoader(applicationContext));
203                for (String library : sls) {
204                    SharedLibrary sl = container.getRegistry().getSharedLibrary(library);
205                    parents.add(sl.getClassLoader());
206                }
207                classLoader = new JarFileClassLoader(applicationContext.getDisplayName(), 
208                                                     urls, 
209                                                     parents.toArray(new ClassLoader[parents.size()]),
210                                                     inverse,
211                                                     hidden.toArray(new String[hidden.size()]),
212                                                     nonOverridable.toArray(new String[nonOverridable.size()]));
213    
214                // remove the classpath element so Spring doesn't get confused
215                document.getDocumentElement().removeChild(classpathElement);
216            }
217            return classLoader;
218        }
219    
220        private static ClassLoader getParentClassLoader(SpringApplicationContext applicationContext) {
221            ClassLoader classLoader = applicationContext.getClassLoader();
222            if (classLoader == null) {
223                classLoader = Thread.currentThread().getContextClassLoader();
224            }
225            if (classLoader == null) {
226                classLoader = SpringLoader.class.getClassLoader();
227            }
228            return classLoader;
229        }
230        
231    }