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.xbean;
018    
019    import java.io.File;
020    import java.io.FilenameFilter;
021    import java.net.MalformedURLException;
022    import java.net.URI;
023    import java.net.URL;
024    import java.util.ArrayList;
025    import java.util.List;
026    import java.util.ListIterator;
027    
028    import javax.xml.parsers.DocumentBuilder;
029    
030    import org.w3c.dom.Document;
031    import org.w3c.dom.Element;
032    import org.w3c.dom.NodeList;
033    import org.w3c.dom.Text;
034    
035    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
036    import org.apache.xbean.classloader.JarFileClassLoader;
037    import org.apache.xbean.spring.context.SpringApplicationContext;
038    import org.apache.xbean.spring.context.SpringXmlPreprocessor;
039    import org.springframework.beans.FatalBeanException;
040    import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
041    
042    /**
043     * An advanced xml preprocessor that will create a default classloader for the SU if none
044     * is configured.
045     * 
046     * @author gnodet
047     */
048    public class ClassLoaderXmlPreprocessor implements SpringXmlPreprocessor {
049    
050        public static final String CLASSPATH_XML = "classpath.xml";
051        public static final String LIB_DIR = "/lib";
052        
053        private final File root;
054        
055        public ClassLoaderXmlPreprocessor(File root) {
056            this.root = root;
057        }
058    
059        public void preprocess(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
060            // determine the classLoader
061            ClassLoader classLoader;
062            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
063            if (classpathElements.getLength() == 0) {
064                // Check if a classpath.xml file exists in the root of the SU
065                URL url = getResource(CLASSPATH_XML);
066                if (url != null) {
067                    try {
068                        DocumentBuilder builder = new SourceTransformer().createDocumentBuilder();
069                        Document doc = builder.parse(url.toString());
070                        classLoader = getClassLoader(applicationContext, reader, doc);
071                    } catch (Exception e) {
072                        throw new FatalBeanException("Unable to load classpath.xml file", e);
073                    }
074                } else {
075                    try {
076                        URL[] urls = getDefaultLocations();
077                        ClassLoader parentLoader = getParentClassLoader(applicationContext);
078                        classLoader = new JarFileClassLoader(applicationContext.getDisplayName(), urls, parentLoader);
079                        // assign the class loader to the xml reader and the
080                        // application context
081                    } catch (Exception e) {
082                        throw new FatalBeanException("Unable to create default classloader for SU", e);
083                    }
084                }
085            } else {
086                classLoader = getClassLoader(applicationContext, reader, document);
087            }
088            reader.setBeanClassLoader(classLoader);
089            applicationContext.setClassLoader(classLoader);
090            Thread.currentThread().setContextClassLoader(classLoader);
091        }
092    
093        protected URL getResource(String location) {
094            URI uri = root.toURI().resolve(location);
095            File file = new File(uri);
096    
097            if (!file.canRead()) {
098                return null;
099            }
100    
101            try {
102                return file.toURL();
103            } catch (MalformedURLException e) {
104                throw new IllegalArgumentException("Malformed resource " + uri);
105            }
106        }
107        
108        protected URL[] getDefaultLocations() {
109            try {
110                File[] jars = new File(root, LIB_DIR).listFiles(new FilenameFilter() {
111                    public boolean accept(File dir, String name) {
112                        name = name.toLowerCase();
113                        return name.endsWith(".jar") || name.endsWith(".zip");
114                    }
115                });
116                URL[] urls = new URL[jars != null ? jars.length + 1 : 1];
117                urls[0] = root.toURL();
118                if (jars != null) {
119                    for (int i = 0; i < jars.length; i++) {
120                        urls[i + 1] = jars[i].toURL();
121                    }
122                }
123                return urls;
124            } catch (MalformedURLException e) {
125                throw new FatalBeanException("Unable to get default classpath locations", e);
126            }
127        }
128        
129        protected ClassLoader getClassLoader(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
130            // determine the classLoader
131            ClassLoader classLoader;
132            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
133            if (classpathElements.getLength() < 1) {
134                classLoader = getParentClassLoader(applicationContext);
135            } else if (classpathElements.getLength() > 1) {
136                throw new FatalBeanException("Expected only classpath element but found " + classpathElements.getLength());
137            } else {
138                Element classpathElement = (Element) classpathElements.item(0);
139                
140                // Delegation mode
141                boolean inverse = false;
142                String inverseAttr = classpathElement.getAttribute("inverse");
143                if (inverseAttr != null && "true".equalsIgnoreCase(inverseAttr)) {
144                    inverse = true;
145                }
146    
147                // build hidden classes
148                List<String> hidden = new ArrayList<String>();
149                NodeList hiddenElems = classpathElement.getElementsByTagName("hidden");
150                for (int i = 0; i < hiddenElems.getLength(); i++) {
151                    Element hiddenElement = (Element) hiddenElems.item(i);
152                    String pattern = ((Text) hiddenElement.getFirstChild()).getData().trim();
153                    hidden.add(pattern);
154                }
155    
156                // build non overridable classes
157                List<String> nonOverridable = new ArrayList<String>();
158                NodeList nonOverridableElems = classpathElement.getElementsByTagName("nonOverridable");
159                for (int i = 0; i < nonOverridableElems.getLength(); i++) {
160                    Element nonOverridableElement = (Element) nonOverridableElems.item(i);
161                    String pattern = ((Text) nonOverridableElement.getFirstChild()).getData().trim();
162                    nonOverridable.add(pattern);
163                }
164    
165                // build the classpath
166                List<String> classpath = new ArrayList<String>();
167                NodeList locations = classpathElement.getElementsByTagName("location");
168                for (int i = 0; i < locations.getLength(); i++) {
169                    Element locationElement = (Element) locations.item(i);
170                    String location = ((Text) locationElement.getFirstChild()).getData().trim();
171                    classpath.add(location);
172                }
173                
174                // convert the paths to URLS
175                URL[] urls;
176                if (classpath.size() != 0) {
177                    urls = new URL[classpath.size()];
178                    for (ListIterator<String> iterator = classpath.listIterator(); iterator.hasNext();) {
179                        String location = iterator.next();
180                        URL url = getResource(location);
181                        if (url == null) {
182                            throw new FatalBeanException("Unable to resolve classpath location " + location);
183                        }
184                        urls[iterator.previousIndex()] = url;
185                    }
186                } else {
187                    urls = getDefaultLocations();
188                }
189    
190                // create the classloader
191                List<ClassLoader> parents = new ArrayList<ClassLoader>();
192                parents.add(getParentClassLoader(applicationContext));
193                classLoader = new JarFileClassLoader(applicationContext.getDisplayName(),
194                                                     urls, 
195                                                     parents.toArray(new ClassLoader[parents.size()]),
196                                                     inverse,
197                                                     hidden.toArray(new String[hidden.size()]),
198                                                     nonOverridable.toArray(new String[nonOverridable.size()]));
199    
200                // remove the classpath element so Spring doesn't get confused
201                document.getDocumentElement().removeChild(classpathElement);
202            }
203            return classLoader;
204        }
205    
206        private ClassLoader getParentClassLoader(SpringApplicationContext applicationContext) {
207            ClassLoader classLoader = applicationContext.getClassLoader();
208            if (classLoader == null) {
209                classLoader = Thread.currentThread().getContextClassLoader();
210            }
211            if (classLoader == null) {
212                classLoader = getClass().getClassLoader();
213            }
214            return classLoader;
215        }
216        
217    }