001/* 002 * Copyright 2012-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.cli.compiler; 018 019import java.io.ByteArrayInputStream; 020import java.io.File; 021import java.io.InputStream; 022import java.net.MalformedURLException; 023import java.net.URL; 024import java.net.URLClassLoader; 025import java.security.AccessController; 026import java.security.PrivilegedAction; 027import java.util.ArrayList; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Map; 031import java.util.Set; 032 033import groovy.lang.GroovyClassLoader; 034import org.codehaus.groovy.ast.ClassNode; 035import org.codehaus.groovy.control.CompilationUnit; 036import org.codehaus.groovy.control.CompilerConfiguration; 037import org.codehaus.groovy.control.SourceUnit; 038 039import org.springframework.util.Assert; 040import org.springframework.util.FileCopyUtils; 041import org.springframework.util.StringUtils; 042 043/** 044 * Extension of the {@link GroovyClassLoader} with support for obtaining '.class' files as 045 * resources. 046 * 047 * @author Phillip Webb 048 * @author Dave Syer 049 */ 050public class ExtendedGroovyClassLoader extends GroovyClassLoader { 051 052 private static final String SHARED_PACKAGE = "org.springframework.boot.groovy"; 053 054 private static final URL[] NO_URLS = new URL[] {}; 055 056 private final Map<String, byte[]> classResources = new HashMap<String, byte[]>(); 057 058 private final GroovyCompilerScope scope; 059 060 private final CompilerConfiguration configuration; 061 062 public ExtendedGroovyClassLoader(GroovyCompilerScope scope) { 063 this(scope, createParentClassLoader(scope), new CompilerConfiguration()); 064 } 065 066 private static ClassLoader createParentClassLoader(GroovyCompilerScope scope) { 067 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 068 if (scope == GroovyCompilerScope.DEFAULT) { 069 classLoader = new DefaultScopeParentClassLoader(classLoader); 070 } 071 return classLoader; 072 } 073 074 private ExtendedGroovyClassLoader(GroovyCompilerScope scope, ClassLoader parent, 075 CompilerConfiguration configuration) { 076 super(parent, configuration); 077 this.configuration = configuration; 078 this.scope = scope; 079 } 080 081 @Override 082 protected Class<?> findClass(String name) throws ClassNotFoundException { 083 try { 084 return super.findClass(name); 085 } 086 catch (ClassNotFoundException ex) { 087 if (this.scope == GroovyCompilerScope.DEFAULT 088 && name.startsWith(SHARED_PACKAGE)) { 089 Class<?> sharedClass = findSharedClass(name); 090 if (sharedClass != null) { 091 return sharedClass; 092 } 093 } 094 throw ex; 095 } 096 } 097 098 private Class<?> findSharedClass(String name) { 099 try { 100 String path = name.replace('.', '/').concat(".class"); 101 InputStream inputStream = getParent().getResourceAsStream(path); 102 if (inputStream != null) { 103 try { 104 return defineClass(name, FileCopyUtils.copyToByteArray(inputStream)); 105 } 106 finally { 107 inputStream.close(); 108 } 109 } 110 return null; 111 } 112 catch (Exception ex) { 113 return null; 114 } 115 } 116 117 @Override 118 public InputStream getResourceAsStream(String name) { 119 InputStream resourceStream = super.getResourceAsStream(name); 120 if (resourceStream == null) { 121 byte[] bytes = this.classResources.get(name); 122 resourceStream = bytes == null ? null : new ByteArrayInputStream(bytes); 123 } 124 return resourceStream; 125 } 126 127 @Override 128 public ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { 129 InnerLoader loader = AccessController 130 .doPrivileged(new PrivilegedAction<InnerLoader>() { 131 @Override 132 public InnerLoader run() { 133 return new InnerLoader(ExtendedGroovyClassLoader.this) { 134 // Don't return URLs from the inner loader so that Tomcat only 135 // searches the parent. Fixes 'TLD skipped' issues 136 @Override 137 public URL[] getURLs() { 138 return NO_URLS; 139 } 140 }; 141 } 142 }); 143 return new ExtendedClassCollector(loader, unit, su); 144 } 145 146 public CompilerConfiguration getConfiguration() { 147 return this.configuration; 148 } 149 150 /** 151 * Inner collector class used to track as classes are added. 152 */ 153 protected class ExtendedClassCollector extends ClassCollector { 154 155 protected ExtendedClassCollector(InnerLoader loader, CompilationUnit unit, 156 SourceUnit su) { 157 super(loader, unit, su); 158 } 159 160 @Override 161 protected Class<?> createClass(byte[] code, ClassNode classNode) { 162 Class<?> createdClass = super.createClass(code, classNode); 163 ExtendedGroovyClassLoader.this.classResources 164 .put(classNode.getName().replace('.', '/') + ".class", code); 165 return createdClass; 166 } 167 168 } 169 170 /** 171 * ClassLoader used for a parent that filters so that only classes from groovy-all.jar 172 * are exposed. 173 */ 174 private static class DefaultScopeParentClassLoader extends ClassLoader { 175 176 private static final String[] GROOVY_JARS_PREFIXES = { "groovy", "antlr", "asm" }; 177 178 private final URLClassLoader groovyOnlyClassLoader; 179 180 DefaultScopeParentClassLoader(ClassLoader parent) { 181 super(parent); 182 this.groovyOnlyClassLoader = new URLClassLoader(getGroovyJars(parent), null); 183 } 184 185 private URL[] getGroovyJars(final ClassLoader parent) { 186 Set<URL> urls = new HashSet<URL>(); 187 findGroovyJarsDirectly(parent, urls); 188 if (urls.isEmpty()) { 189 findGroovyJarsFromClassPath(parent, urls); 190 } 191 Assert.state(!urls.isEmpty(), "Unable to find groovy JAR"); 192 return new ArrayList<URL>(urls).toArray(new URL[urls.size()]); 193 } 194 195 private void findGroovyJarsDirectly(ClassLoader classLoader, Set<URL> urls) { 196 while (classLoader != null) { 197 if (classLoader instanceof URLClassLoader) { 198 for (URL url : ((URLClassLoader) classLoader).getURLs()) { 199 if (isGroovyJar(url.toString())) { 200 urls.add(url); 201 } 202 } 203 } 204 classLoader = classLoader.getParent(); 205 } 206 } 207 208 private void findGroovyJarsFromClassPath(ClassLoader parent, Set<URL> urls) { 209 String classpath = System.getProperty("java.class.path"); 210 String[] entries = classpath.split(System.getProperty("path.separator")); 211 for (String entry : entries) { 212 if (isGroovyJar(entry)) { 213 File file = new File(entry); 214 if (file.canRead()) { 215 try { 216 urls.add(file.toURI().toURL()); 217 } 218 catch (MalformedURLException ex) { 219 // Swallow and continue 220 } 221 } 222 } 223 } 224 } 225 226 private boolean isGroovyJar(String entry) { 227 entry = StringUtils.cleanPath(entry); 228 for (String jarPrefix : GROOVY_JARS_PREFIXES) { 229 if (entry.contains("/" + jarPrefix + "-")) { 230 return true; 231 } 232 } 233 return false; 234 } 235 236 @Override 237 protected Class<?> loadClass(String name, boolean resolve) 238 throws ClassNotFoundException { 239 this.groovyOnlyClassLoader.loadClass(name); 240 return super.loadClass(name, resolve); 241 } 242 243 } 244 245}