16
16
17
17
package org .springframework .boot .testsupport .runner .classpath ;
18
18
19
+ import java .io .File ;
20
+ import java .lang .management .ManagementFactory ;
21
+ import java .net .URISyntaxException ;
19
22
import java .net .URL ;
20
23
import java .net .URLClassLoader ;
24
+ import java .util .ArrayList ;
25
+ import java .util .Arrays ;
26
+ import java .util .Collections ;
27
+ import java .util .List ;
28
+ import java .util .jar .Attributes ;
29
+ import java .util .jar .JarFile ;
30
+ import java .util .regex .Pattern ;
31
+ import java .util .stream .Stream ;
32
+
33
+ import org .apache .maven .repository .internal .MavenRepositorySystemUtils ;
34
+ import org .eclipse .aether .DefaultRepositorySystemSession ;
35
+ import org .eclipse .aether .RepositorySystem ;
36
+ import org .eclipse .aether .artifact .DefaultArtifact ;
37
+ import org .eclipse .aether .collection .CollectRequest ;
38
+ import org .eclipse .aether .connector .basic .BasicRepositoryConnectorFactory ;
39
+ import org .eclipse .aether .graph .Dependency ;
40
+ import org .eclipse .aether .impl .DefaultServiceLocator ;
41
+ import org .eclipse .aether .repository .LocalRepository ;
42
+ import org .eclipse .aether .repository .RemoteRepository ;
43
+ import org .eclipse .aether .resolution .ArtifactResult ;
44
+ import org .eclipse .aether .resolution .DependencyRequest ;
45
+ import org .eclipse .aether .resolution .DependencyResult ;
46
+ import org .eclipse .aether .spi .connector .RepositoryConnectorFactory ;
47
+ import org .eclipse .aether .spi .connector .transport .TransporterFactory ;
48
+ import org .eclipse .aether .transport .http .HttpTransporterFactory ;
49
+
50
+ import org .springframework .core .annotation .MergedAnnotation ;
51
+ import org .springframework .core .annotation .MergedAnnotations ;
52
+ import org .springframework .util .AntPathMatcher ;
53
+ import org .springframework .util .StringUtils ;
21
54
22
55
/**
23
56
* Custom {@link URLClassLoader} that modifies the class path.
24
57
*
25
58
* @author Andy Wilkinson
26
59
* @author Christoph Dreis
27
- * @see ModifiedClassPathClassLoaderFactory
28
60
*/
29
61
final class ModifiedClassPathClassLoader extends URLClassLoader {
30
62
63
+ private static final Pattern INTELLIJ_CLASSPATH_JAR_PATTERN = Pattern .compile (".*classpath(\\ d+)?\\ .jar" );
64
+
31
65
private final ClassLoader junitLoader ;
32
66
33
67
ModifiedClassPathClassLoader (URL [] urls , ClassLoader parent , ClassLoader junitLoader ) {
@@ -43,4 +77,178 @@ public Class<?> loadClass(String name) throws ClassNotFoundException {
43
77
return super .loadClass (name );
44
78
}
45
79
80
+ static ModifiedClassPathClassLoader get (Class <?> testClass ) {
81
+ ClassLoader classLoader = testClass .getClassLoader ();
82
+ return new ModifiedClassPathClassLoader (processUrls (extractUrls (classLoader ), testClass ),
83
+ classLoader .getParent (), classLoader );
84
+ }
85
+
86
+ private static URL [] extractUrls (ClassLoader classLoader ) {
87
+ List <URL > extractedUrls = new ArrayList <>();
88
+ doExtractUrls (classLoader ).forEach ((URL url ) -> {
89
+ if (isManifestOnlyJar (url )) {
90
+ extractedUrls .addAll (extractUrlsFromManifestClassPath (url ));
91
+ }
92
+ else {
93
+ extractedUrls .add (url );
94
+ }
95
+ });
96
+ return extractedUrls .toArray (new URL [0 ]);
97
+ }
98
+
99
+ private static Stream <URL > doExtractUrls (ClassLoader classLoader ) {
100
+ if (classLoader instanceof URLClassLoader ) {
101
+ return Stream .of (((URLClassLoader ) classLoader ).getURLs ());
102
+ }
103
+ return Stream .of (ManagementFactory .getRuntimeMXBean ().getClassPath ().split (File .pathSeparator ))
104
+ .map (ModifiedClassPathClassLoader ::toURL );
105
+ }
106
+
107
+ private static URL toURL (String entry ) {
108
+ try {
109
+ return new File (entry ).toURI ().toURL ();
110
+ }
111
+ catch (Exception ex ) {
112
+ throw new IllegalArgumentException (ex );
113
+ }
114
+ }
115
+
116
+ private static boolean isManifestOnlyJar (URL url ) {
117
+ return isSurefireBooterJar (url ) || isShortenedIntelliJJar (url );
118
+ }
119
+
120
+ private static boolean isSurefireBooterJar (URL url ) {
121
+ return url .getPath ().contains ("surefirebooter" );
122
+ }
123
+
124
+ private static boolean isShortenedIntelliJJar (URL url ) {
125
+ String urlPath = url .getPath ();
126
+ boolean isCandidate = INTELLIJ_CLASSPATH_JAR_PATTERN .matcher (urlPath ).matches ();
127
+ if (isCandidate ) {
128
+ try {
129
+ Attributes attributes = getManifestMainAttributesFromUrl (url );
130
+ String createdBy = attributes .getValue ("Created-By" );
131
+ return createdBy != null && createdBy .contains ("IntelliJ" );
132
+ }
133
+ catch (Exception ex ) {
134
+ }
135
+ }
136
+ return false ;
137
+ }
138
+
139
+ private static List <URL > extractUrlsFromManifestClassPath (URL booterJar ) {
140
+ List <URL > urls = new ArrayList <>();
141
+ try {
142
+ for (String entry : getClassPath (booterJar )) {
143
+ urls .add (new URL (entry ));
144
+ }
145
+ }
146
+ catch (Exception ex ) {
147
+ throw new RuntimeException (ex );
148
+ }
149
+ return urls ;
150
+ }
151
+
152
+ private static String [] getClassPath (URL booterJar ) throws Exception {
153
+ Attributes attributes = getManifestMainAttributesFromUrl (booterJar );
154
+ return StringUtils .delimitedListToStringArray (attributes .getValue (Attributes .Name .CLASS_PATH ), " " );
155
+ }
156
+
157
+ private static Attributes getManifestMainAttributesFromUrl (URL url ) throws Exception {
158
+ try (JarFile jarFile = new JarFile (new File (url .toURI ()))) {
159
+ return jarFile .getManifest ().getMainAttributes ();
160
+ }
161
+ }
162
+
163
+ private static URL [] processUrls (URL [] urls , Class <?> testClass ) {
164
+ MergedAnnotations annotations = MergedAnnotations .from (testClass , MergedAnnotations .SearchStrategy .EXHAUSTIVE );
165
+ ClassPathEntryFilter filter = new ClassPathEntryFilter (annotations .get (ClassPathExclusions .class ));
166
+ List <URL > processedUrls = new ArrayList <>();
167
+ List <URL > additionalUrls = getAdditionalUrls (annotations .get (ClassPathOverrides .class ));
168
+ processedUrls .addAll (additionalUrls );
169
+ for (URL url : urls ) {
170
+ if (!filter .isExcluded (url )) {
171
+ processedUrls .add (url );
172
+ }
173
+ }
174
+ return processedUrls .toArray (new URL [0 ]);
175
+ }
176
+
177
+ private static List <URL > getAdditionalUrls (MergedAnnotation <ClassPathOverrides > annotation ) {
178
+ if (!annotation .isPresent ()) {
179
+ return Collections .emptyList ();
180
+ }
181
+ return resolveCoordinates (annotation .getStringArray (MergedAnnotation .VALUE ));
182
+ }
183
+
184
+ private static List <URL > resolveCoordinates (String [] coordinates ) {
185
+ DefaultServiceLocator serviceLocator = MavenRepositorySystemUtils .newServiceLocator ();
186
+ serviceLocator .addService (RepositoryConnectorFactory .class , BasicRepositoryConnectorFactory .class );
187
+ serviceLocator .addService (TransporterFactory .class , HttpTransporterFactory .class );
188
+ RepositorySystem repositorySystem = serviceLocator .getService (RepositorySystem .class );
189
+ DefaultRepositorySystemSession session = MavenRepositorySystemUtils .newSession ();
190
+ LocalRepository localRepository = new LocalRepository (System .getProperty ("user.home" ) + "/.m2/repository" );
191
+ session .setLocalRepositoryManager (repositorySystem .newLocalRepositoryManager (session , localRepository ));
192
+ CollectRequest collectRequest = new CollectRequest (null , Arrays .asList (
193
+ new RemoteRepository .Builder ("central" , "default" , "https://repo.maven.apache.org/maven2" ).build ()));
194
+
195
+ collectRequest .setDependencies (createDependencies (coordinates ));
196
+ DependencyRequest dependencyRequest = new DependencyRequest (collectRequest , null );
197
+ try {
198
+ DependencyResult result = repositorySystem .resolveDependencies (session , dependencyRequest );
199
+ List <URL > resolvedArtifacts = new ArrayList <>();
200
+ for (ArtifactResult artifact : result .getArtifactResults ()) {
201
+ resolvedArtifacts .add (artifact .getArtifact ().getFile ().toURI ().toURL ());
202
+ }
203
+ return resolvedArtifacts ;
204
+ }
205
+ catch (Exception ignored ) {
206
+ return Collections .emptyList ();
207
+
208
+ }
209
+ }
210
+
211
+ private static List <Dependency > createDependencies (String [] allCoordinates ) {
212
+ List <Dependency > dependencies = new ArrayList <>();
213
+ for (String coordinate : allCoordinates ) {
214
+ dependencies .add (new Dependency (new DefaultArtifact (coordinate ), null ));
215
+ }
216
+ return dependencies ;
217
+ }
218
+
219
+ /**
220
+ * Filter for class path entries.
221
+ */
222
+ private static final class ClassPathEntryFilter {
223
+
224
+ private final List <String > exclusions ;
225
+
226
+ private final AntPathMatcher matcher = new AntPathMatcher ();
227
+
228
+ private ClassPathEntryFilter (MergedAnnotation <ClassPathExclusions > annotation ) {
229
+ this .exclusions = new ArrayList <>();
230
+ this .exclusions .add ("log4j-*.jar" );
231
+ if (annotation .isPresent ()) {
232
+ this .exclusions .addAll (Arrays .asList (annotation .getStringArray (MergedAnnotation .VALUE )));
233
+ }
234
+ }
235
+
236
+ private boolean isExcluded (URL url ) {
237
+ if ("file" .equals (url .getProtocol ())) {
238
+ try {
239
+ String name = new File (url .toURI ()).getName ();
240
+ for (String exclusion : this .exclusions ) {
241
+ if (this .matcher .match (exclusion , name )) {
242
+ return true ;
243
+ }
244
+ }
245
+ }
246
+ catch (URISyntaxException ex ) {
247
+ }
248
+ }
249
+ return false ;
250
+ }
251
+
252
+ }
253
+
46
254
}
0 commit comments