View Javadoc
1   package org.apache.maven.plugin.coreit;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.plugin.AbstractMojo;
23  import org.apache.maven.plugin.MojoExecutionException;
24  
25  import java.io.File;
26  import java.io.IOException;
27  import java.lang.reflect.Method;
28  import java.lang.reflect.Modifier;
29  import java.net.URL;
30  import java.net.URLClassLoader;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Properties;
35  
36  /**
37   * Loads classes and/or resources from a class loader and records the results in a properties file.
38   *
39   * @author Benjamin Bentmann
40   *
41   */
42  public abstract class AbstractLoadMojo
43      extends AbstractMojo
44  {
45  
46      /**
47       * The comma separated set of classes to load. For each specified qualified class name <code>QCN</code> that was
48       * successfully loaded, the generated properties files will contain a key named <code>QCN</code>. The value of this
49       * key will be the hash code of the requested class. In addition, a key named <code>QCN.methods</code> holds the
50       * comma separated list of all public methods declared directly in that class, in alphabetic order and possibly with
51       * duplicates to account for overloaded methods.
52       *
53       * @parameter property="clsldr.classNames"
54       */
55      protected String classNames;
56  
57      /**
58       * The comma separated set of resources to load. For each specified absolute resource path <code>ARP</code> that was
59       * successfully loaded, the generated properties files will contain a key named <code>ARP</code> whose value gives
60       * the URL to the resource. In addition, the keys <code>ARP.count</code>, <code>ARP.0</code>, <code>ARP.1</code>
61       * etc. will enumerate all URLs matching the resource name.
62       *
63       * @parameter property="clsldr.resourcePaths"
64       */
65      protected String resourcePaths;
66  
67      /**
68       * Loads the classes/resources.
69       *
70       * @param outputFile  The path to the properties file to generate, must not be <code>null</code>.
71       * @param classLoader The class loader to use, must not be <code>null</code>.
72       * @throws MojoExecutionException If the output file could not be created.
73       */
74      protected void execute( File outputFile, ClassLoader classLoader )
75          throws MojoExecutionException
76      {
77          getLog().info( "[MAVEN-CORE-IT-LOG] Using class loader " + classLoader );
78  
79          /*
80           * NOTE: This one is a little subtle. For all properly implemented class loaders, loading a class/resource from
81           * a child class loader (with the usual parent-first delegation and no additional search path) will deliver the
82           * same result as loading directly from the parent class loader. However, Maven or better Plexus Classworlds
83           * employs custom class loaders which - as history has shown (MNG-1898) - might not always be cleanly
84           * implemented. To catch potential class loader defects, we check both the results from the original class
85           * loader and a delegating child class loader for consistency. The key point is that querying the child class
86           * loader will use a slightly different code path in the original class loader during parent delegation, thereby
87           * increasing test coverage for its implementation.
88           */
89          ClassLoader childClassLoader = new URLClassLoader( new URL[0], classLoader );
90  
91          Properties loaderProperties = new Properties();
92  
93          if ( classNames != null && classNames.length() > 0 )
94          {
95              String[] names = classNames.split( "," );
96              for ( String name : names )
97              {
98                  getLog().info( "[MAVEN-CORE-IT-LOG] Loading class " + name );
99  
100                 // test ClassLoader.loadClass(String) and (indirectly) ClassLoader.loadClass(String, boolean)
101                 try
102                 {
103                     Class type = classLoader.loadClass( name );
104                     getLog().info( "[MAVEN-CORE-IT-LOG]   Loaded class from " + type.getClassLoader() );
105                     try
106                     {
107                         if ( !type.equals( childClassLoader.loadClass( name ) ) )
108                         {
109                             throw new ClassNotFoundException( name );
110                         }
111                     }
112                     catch ( ClassNotFoundException cnfe )
113                     {
114                         getLog().error( "[MAVEN-CORE-IT-LOG] Detected class loader defect while loading " + name );
115                         throw cnfe;
116                     }
117                     loaderProperties.setProperty( name, "" + type.hashCode() );
118 
119                     Method[] methods = type.getDeclaredMethods();
120                     List methodNames = new ArrayList();
121                     for ( Method method : methods )
122                     {
123                         if ( Modifier.isPublic( method.getModifiers() ) )
124                         {
125                             methodNames.add( method.getName() );
126                         }
127                     }
128                     Collections.sort( methodNames );
129                     StringBuilder buffer = new StringBuilder( 1024 );
130                     for ( Object methodName : methodNames )
131                     {
132                         if ( buffer.length() > 0 )
133                         {
134                             buffer.append( ',' );
135                         }
136                         buffer.append( methodName );
137                     }
138 
139                     loaderProperties.setProperty( name + ".methods", buffer.toString() );
140                 }
141                 catch ( ClassNotFoundException e )
142                 {
143                     // ignore, will be reported by means of missing keys in the properties file
144                     getLog().info( "[MAVEN-CORE-IT-LOG]   Class not available" );
145                 }
146                 catch ( LinkageError e )
147                 {
148                     // ignore, will be reported by means of missing keys in the properties file
149                     getLog().info( "[MAVEN-CORE-IT-LOG]   Class not linkable", e );
150                 }
151             }
152         }
153 
154         if ( resourcePaths != null && resourcePaths.length() > 0 )
155         {
156             String[] paths = resourcePaths.split( "," );
157             for ( String path : paths )
158             {
159                 getLog().info( "[MAVEN-CORE-IT-LOG] Loading resource " + path );
160 
161                 // test ClassLoader.getResource()
162                 URL url = classLoader.getResource( path );
163                 getLog().info( "[MAVEN-CORE-IT-LOG]   Loaded resource from " + url );
164                 if ( url != null && !url.equals( childClassLoader.getResource( path ) ) )
165                 {
166                     getLog().error( "[MAVEN-CORE-IT-LOG] Detected class loader defect while getting " + path );
167                     url = null;
168                 }
169                 if ( url != null )
170                 {
171                     loaderProperties.setProperty( path, url.toString() );
172                 }
173 
174                 // test ClassLoader.getResources()
175                 try
176                 {
177                     List urls = Collections.list( classLoader.getResources( path ) );
178                     if ( !urls.equals( Collections.list( childClassLoader.getResources( path ) ) ) )
179                     {
180                         getLog().error( "[MAVEN-CORE-IT-LOG] Detected class loader defect while getting " + path );
181                         urls = Collections.EMPTY_LIST;
182                     }
183                     loaderProperties.setProperty( path + ".count", "" + urls.size() );
184                     for ( int j = 0; j < urls.size(); j++ )
185                     {
186                         loaderProperties.setProperty( path + "." + j, urls.get( j ).toString() );
187                     }
188                 }
189                 catch ( IOException e )
190                 {
191                     throw new MojoExecutionException( "Resources could not be enumerated: " + path, e );
192                 }
193             }
194         }
195 
196         getLog().info( "[MAVEN-CORE-IT-LOG] Creating output file " + outputFile );
197 
198         PropertiesUtil.write( outputFile, loaderProperties );
199 
200         getLog().info( "[MAVEN-CORE-IT-LOG] Created output file " + outputFile );
201     }
202 
203 }