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