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.framework.ComponentMBeanImpl;
032    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
033    import org.apache.xbean.classloader.JarFileClassLoader;
034    import org.apache.xbean.server.repository.FileSystemRepository;
035    import org.apache.xbean.server.repository.Repository;
036    import org.apache.xbean.server.spring.loader.SpringLoader;
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    import org.w3c.dom.Document;
042    import org.w3c.dom.Element;
043    import org.w3c.dom.NodeList;
044    import org.w3c.dom.Text;
045    
046    /**
047     * An advanced xml preprocessor that will create a default classloader for the SU if none
048     * is configured.
049     * 
050     * @author gnodet
051     */
052    public class ClassLoaderXmlPreprocessor implements SpringXmlPreprocessor {
053    
054        public static final String CLASSPATH_XML = "classpath.xml";
055        public static final String LIB_DIR = "/lib";
056        
057        private final FileSystemRepository repository;
058        private final JBIContainer container;
059        
060        public ClassLoaderXmlPreprocessor(Repository repository) {
061            this(repository, null);
062        }
063    
064        public ClassLoaderXmlPreprocessor(Repository repository, JBIContainer container) {
065            if (repository instanceof FileSystemRepository == false) {
066                throw new IllegalArgumentException("repository must be a FileSystemRepository");
067            }
068            this.repository = (FileSystemRepository) repository;
069            this.container = container;
070        }
071    
072        public void preprocess(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
073            // determine the classLoader
074            ClassLoader classLoader;
075            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
076            if (classpathElements.getLength() == 0) {
077                // Check if a classpath.xml file exists in the root of the SU
078                URL url = repository.getResource(CLASSPATH_XML);
079                if (url != null) {
080                    try {
081                        DocumentBuilder builder = new SourceTransformer().createDocumentBuilder();
082                        Document doc = builder.parse(url.toString());
083                        classLoader = getClassLoader(applicationContext, reader, doc);
084                    } catch (Exception e) {
085                        throw new FatalBeanException("Unable to load classpath.xml file", e);
086                    }
087                } else {
088                    try {
089                        URL[] urls = getDefaultLocations();
090                        ClassLoader parentLoader = getParentClassLoader(applicationContext);
091                        classLoader = new JarFileClassLoader(applicationContext.getDisplayName(), urls, parentLoader);
092                        // assign the class loader to the xml reader and the
093                        // application context
094                    } catch (Exception e) {
095                        throw new FatalBeanException("Unable to create default classloader for SU", e);
096                    }
097                }
098            } else {
099                classLoader = getClassLoader(applicationContext, reader, document);
100            }
101            reader.setBeanClassLoader(classLoader);
102            applicationContext.setClassLoader(classLoader);
103            Thread.currentThread().setContextClassLoader(classLoader);
104        }
105        
106        protected URL[] getDefaultLocations() {
107            try {
108                File root = repository.getRoot();
109                File[] jars = new File(root, LIB_DIR).listFiles(new FilenameFilter() {
110                    public boolean accept(File dir, String name) {
111                        name = name.toLowerCase();
112                        return name.endsWith(".jar") || name.endsWith(".zip");
113                    }
114                });
115                URL[] urls = new URL[jars != null ? jars.length + 1 : 1];
116                urls[0] = root.toURL();
117                if (jars != null) {
118                    for (int i = 0; i < jars.length; i++) {
119                        urls[i+1] = jars[i].toURL();
120                    }
121                }
122                return urls;
123            } catch (MalformedURLException e) {
124                throw new FatalBeanException("Unable to get default classpath locations", e);
125            }
126        }
127        
128        protected ClassLoader getClassLoader(SpringApplicationContext applicationContext, XmlBeanDefinitionReader reader, Document document) {
129            // determine the classLoader
130            ClassLoader classLoader;
131            NodeList classpathElements = document.getDocumentElement().getElementsByTagName("classpath");
132            if (classpathElements.getLength() < 1) {
133                classLoader = getParentClassLoader(applicationContext);
134            } else if (classpathElements.getLength() > 1) {
135                throw new FatalBeanException("Expected only classpath element but found " + classpathElements.getLength());
136            } else {
137                Element classpathElement = (Element) classpathElements.item(0);
138                
139                // Delegation mode
140                boolean inverse = false;
141                String inverseAttr = classpathElement.getAttribute("inverse");
142                if (inverseAttr != null && "true".equalsIgnoreCase(inverseAttr)) {
143                    inverse = true;
144                }
145    
146                // build hidden classes
147                List<String> hidden = new ArrayList<String>();
148                NodeList hiddenElems = classpathElement.getElementsByTagName("hidden");
149                for (int i = 0; i < hiddenElems.getLength(); i++) {
150                    Element hiddenElement = (Element) hiddenElems.item(i);
151                    String pattern = ((Text) hiddenElement.getFirstChild()).getData().trim();
152                    hidden.add(pattern);
153                }
154    
155                // build non overridable classes
156                List<String> nonOverridable = new ArrayList<String>();
157                NodeList nonOverridableElems = classpathElement.getElementsByTagName("nonOverridable");
158                for (int i = 0; i < nonOverridableElems.getLength(); i++) {
159                    Element nonOverridableElement = (Element) nonOverridableElems.item(i);
160                    String pattern = ((Text) nonOverridableElement.getFirstChild()).getData().trim();
161                    nonOverridable.add(pattern);
162                }
163    
164                // build the classpath
165                List<String> classpath = new ArrayList<String>();
166                NodeList locations = classpathElement.getElementsByTagName("location");
167                for (int i = 0; i < locations.getLength(); i++) {
168                    Element locationElement = (Element) locations.item(i);
169                    String location = ((Text) locationElement.getFirstChild()).getData().trim();
170                    classpath.add(location);
171                }
172                
173                // Add shared libraries
174                List<String> sls = new ArrayList<String>();
175                NodeList libraries = classpathElement.getElementsByTagName("library");
176                for (int i = 0; i < libraries.getLength(); i++) {
177                    Element locationElement = (Element) libraries.item(i);
178                    String library = ((Text) locationElement.getFirstChild()).getData().trim();
179                    sls.add(library);
180                }
181                if (sls.size() > 0 && container == null) {
182                    throw new IllegalStateException("Can not reference shared libraries if the component is not deployed in ServiceMix");
183                }
184    
185                // Add components
186                List<String> components = new ArrayList<String>();
187                NodeList componentList = classpathElement.getElementsByTagName("component");
188                for (int i = 0; i < componentList.getLength(); i++) {
189                    Element locationElement = (Element) componentList.item(i);
190                    String component = ((Text) locationElement.getFirstChild()).getData().trim();
191                    components.add(component);
192                }
193                if (components.size() > 0 && container == null) {
194                    throw new IllegalStateException("Can not reference other components if the component is not deployed in ServiceMix");
195                }
196    
197                // convert the paths to URLS
198                URL[] urls;
199                if (classpath.size() != 0) {
200                    urls = new URL[classpath.size()];
201                    for (ListIterator<String> iterator = classpath.listIterator(); iterator.hasNext();) {
202                        String location = iterator.next();
203                        URL url = repository.getResource(location);
204                        if (url == null) {
205                            throw new FatalBeanException("Unable to resolve classpath location " + location);
206                        }
207                        urls[iterator.previousIndex()] = url;
208                    }
209                } else {
210                    urls = getDefaultLocations();
211                }
212    
213                // create the classloader
214                List<ClassLoader> parents = new ArrayList<ClassLoader>();
215                parents.add(getParentClassLoader(applicationContext));
216                for (String library : sls) {
217                    SharedLibrary sl = container.getRegistry().getSharedLibrary(library);
218                    if (sl == null) {
219                        throw new IllegalStateException("No such shared library: " + library);
220                    }
221                    parents.add(sl.getClassLoader());
222                }
223                for (String component : components) {
224                    ComponentMBeanImpl componentMBean = container.getRegistry().getComponent(component);
225                    if (componentMBean == null) {
226                        throw new IllegalStateException("No such component: " + componentMBean);
227                    }
228                    parents.add(componentMBean.getComponent().getClass().getClassLoader());
229                }
230                classLoader = new JarFileClassLoader(applicationContext.getDisplayName(),
231                                                     urls, 
232                                                     parents.toArray(new ClassLoader[parents.size()]),
233                                                     inverse,
234                                                     hidden.toArray(new String[hidden.size()]),
235                                                     nonOverridable.toArray(new String[nonOverridable.size()]));
236    
237                // remove the classpath element so Spring doesn't get confused
238                document.getDocumentElement().removeChild(classpathElement);
239            }
240            return classLoader;
241        }
242    
243        private static ClassLoader getParentClassLoader(SpringApplicationContext applicationContext) {
244            ClassLoader classLoader = applicationContext.getClassLoader();
245            if (classLoader == null) {
246                classLoader = Thread.currentThread().getContextClassLoader();
247            }
248            if (classLoader == null) {
249                classLoader = SpringLoader.class.getClassLoader();
250            }
251            return classLoader;
252        }
253        
254    }