View Javadoc
1   /*
2    * Copyright 2008-2016 Brian Thomas Matthews
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.btmatthews.maven.plugins.ldap.mojo;
18  
19  import org.apache.maven.artifact.Artifact;
20  import org.apache.maven.artifact.InvalidRepositoryException;
21  import org.apache.maven.artifact.repository.ArtifactRepository;
22  import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
23  import org.apache.maven.artifact.repository.RepositoryRequest;
24  import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
25  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
26  import org.apache.maven.project.MavenProject;
27  import org.apache.maven.repository.RepositorySystem;
28  import org.codehaus.plexus.classworlds.realm.ClassRealm;
29  import org.codehaus.plexus.component.annotations.Component;
30  import org.codehaus.plexus.component.annotations.Requirement;
31  import org.codehaus.plexus.component.configurator.AbstractComponentConfigurator;
32  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
33  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
34  import org.codehaus.plexus.component.configurator.ConfigurationListener;
35  import org.codehaus.plexus.component.configurator.converters.composite.ObjectWithFieldsConverter;
36  import org.codehaus.plexus.component.configurator.converters.special.ClassRealmConverter;
37  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
38  import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
39  import org.codehaus.plexus.configuration.PlexusConfiguration;
40  
41  import java.io.File;
42  import java.net.MalformedURLException;
43  import java.net.URL;
44  import java.text.MessageFormat;
45  import java.util.*;
46  import java.util.regex.Pattern;
47  
48  /**
49   * @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
50   * @since 1.2.0
51   */
52  @Component(role = ComponentConfigurator.class, hint = "include-server-dependencies")
53  public class IncludeServerDependenciesComponentConfigurator extends AbstractComponentConfigurator {
54  
55      /**
56       * The plugin configuration element that specifies the LDAP server type.
57       */
58      private static final String SERVER_TYPE_ATTRIBUTE = "serverType";
59      /**
60       * The default LDAP server type.
61       */
62      private static final String DEFAULT_SERVER_TYPE_VALUE = "unboundid";
63      /**
64       * The group id of the main artifact for the LDAP server type.
65       */
66      private static final String DEFAULT_GROUP_ID = "com.btmatthews.maven.plugins.ldap";
67      /**
68       * Used to construct the artifact id of the main artifact for the LDAP server type.
69       */
70      private static final String DEFAULT_ARTIFACT_ID_FORMAT = "server-{0}";
71  
72      @Requirement
73      private RepositorySystem repositorySystem;
74  
75      /**
76       * Configure the Mojo by adding the dependencies for the LDAP server type to the class loader.
77       *
78       * @param component           The Mojo being configured.
79       * @param configuration       The plugin configuration.
80       * @param expressionEvaluator Used to evaluate expressions.
81       * @param containerRealm      Used to build the class loader.
82       * @param listener            Receives notification when configuration is changed.
83       * @throws ComponentConfigurationException If there was a problem resolving or locating any of the dependencies.
84       */
85      @Override
86      public void configureComponent(final Object component,
87                                     final PlexusConfiguration configuration,
88                                     final ExpressionEvaluator expressionEvaluator,
89                                     final ClassRealm containerRealm,
90                                     final ConfigurationListener listener)
91              throws ComponentConfigurationException {
92  
93          final String serverType = getServerType(configuration);
94  
95          if (!serverType.startsWith("dependency-")) {
96              addServerDependenciesToClassRealm(serverType, expressionEvaluator, containerRealm);
97          }
98  
99          converterLookup.registerConverter(new ClassRealmConverter(containerRealm));
100 
101         final ObjectWithFieldsConverter converter = new ObjectWithFieldsConverter();
102 
103         converter.processConfiguration(converterLookup, component, containerRealm, configuration,
104                 expressionEvaluator, listener);
105     }
106 
107     /**
108      * Resolve the dependencies for an LDAP server type and add them to the class loader.
109      *
110      * @param serverType          The LDAP server type.
111      * @param expressionEvaluator Used to evaluate expressions.
112      * @param containerRealm      The class loader.
113      * @throws ComponentConfigurationException If there was a problem resolving or locating any of the dependencies.
114      */
115     private void addServerDependenciesToClassRealm(final String serverType,
116                                                    final ExpressionEvaluator expressionEvaluator,
117                                                    final ClassRealm containerRealm)
118             throws ComponentConfigurationException {
119         final Collection<Artifact> classpathElements = getServerDependencies(serverType, expressionEvaluator);
120         if (classpathElements != null) {
121             for (final URL url : buildURLs(classpathElements)) {
122                 containerRealm.addURL(url);
123             }
124         }
125     }
126 
127     /**
128      * Retrive the URLs used to locate the JAR files for a list of artifacts.
129      *
130      * @param classpathElements The list of artifacts.
131      * @return The list of URLs.
132      * @throws ComponentConfigurationException If any JAR files could not be located.
133      */
134     private List<URL> buildURLs(final Collection<Artifact> classpathElements)
135             throws ComponentConfigurationException {
136         final List<URL> urls = new ArrayList<URL>(classpathElements.size());
137         for (final Artifact classpathElement : classpathElements) {
138             try {
139                 final URL url = classpathElement.getFile().toURI().toURL();
140                 urls.add(url);
141             } catch (final MalformedURLException e) {
142                 throw new ComponentConfigurationException("Unable to access project dependency: " + classpathElement, e);
143             }
144         }
145         return urls;
146     }
147 
148     /**
149      * Resolve the LDAP server type artifact and its dependencies.
150      *
151      * @param serverType          The LDAP server type.
152      * @param expressionEvaluator Used to get the Maven project model and repository session objects.
153      * @return A list of dependencies required for the LDAP server type.
154      * @throws ComponentConfigurationException If the was a problem resolving the dependencies for
155      *                                         the LDAP server type.
156      */
157     private Collection<Artifact> getServerDependencies(final String serverType,
158                                                        final ExpressionEvaluator expressionEvaluator)
159             throws ComponentConfigurationException {
160         try {
161             final MavenProject project = (MavenProject) expressionEvaluator.evaluate("${project}");
162             final String localRepo = (String) expressionEvaluator.evaluate("${settings.localRepository}");
163             final ArtifactRepository localRepository = repositorySystem.createLocalRepository(new File(localRepo));
164             final RepositoryRequest repositoryRequest = new DefaultRepositoryRequest();
165             repositoryRequest.setRemoteRepositories(project.getRemoteArtifactRepositories());
166             repositoryRequest.setLocalRepository(localRepository);
167             final ArtifactResolutionRequest request = new ArtifactResolutionRequest(repositoryRequest);
168             request.setArtifact(getServerArtifact(serverType));
169             request.setResolveTransitively(true);
170             final ArtifactResolutionResult result = repositorySystem.resolve(request);
171             if (result.isSuccess()) {
172                 return result.getArtifacts();
173             }
174             boolean first = true;
175             final StringBuilder builder = new StringBuilder("Cannot resolve dependencies: [");
176             for (final Artifact artifact : result.getMissingArtifacts()) {
177                 if (!first) {
178                     builder.append(',');
179                 } else {
180                     first = false;
181                 }
182                 builder.append(artifact.getGroupId());
183                 builder.append(':');
184                 builder.append(artifact.getArtifactId());
185                 builder.append(':');
186                 builder.append(artifact.getVersion());
187             }
188             builder.append("]");
189             throw new ComponentConfigurationException(builder.toString());
190         } catch (final ExpressionEvaluationException e) {
191             throw new ComponentConfigurationException("Error evaluating expression", e);
192         } catch (final InvalidRepositoryException e) {
193             throw new ComponentConfigurationException("Error resolving local repository", e);
194         }
195     }
196 
197     /**
198      * Get the artifact descriptor for the JAR file that implements support for the LDAP server type.
199      *
200      * @param serverType The LDAP server type.
201      * @return The JAR file artifact descriptor.
202      */
203     private Artifact getServerArtifact(final String serverType)
204             throws ComponentConfigurationException {
205         if (serverType.startsWith("gav-")) {
206             int index = serverType.indexOf("-", 4);
207             if (index > 0) {
208                 String[] gav = serverType.substring(index + 1).split(":");
209                 if (gav.length == 3) {
210                     return repositorySystem.createArtifact(
211                             gav[0],
212                             gav[1],
213                             gav[2],
214                             "runtime",
215                             "jar"
216                     );
217                 }
218             }
219             throw new ComponentConfigurationException("Invalid server type: " + serverType);
220         } else {
221             return repositorySystem.createArtifact(
222                     DEFAULT_GROUP_ID,
223                     MessageFormat.format(DEFAULT_ARTIFACT_ID_FORMAT, serverType),
224                     getClass().getPackage().getImplementationVersion(),
225                     "runtime",
226                     "jar");
227         }
228     }
229 
230     /**
231      * Determine the configured LDAP server type. If one has not been configured then
232      * return the default.
233      *
234      * @param configuration The plugin configuration.
235      * @return The LDAP server type.
236      */
237     private String getServerType(final PlexusConfiguration configuration) {
238         final Pattern pattern = Pattern.compile("\\$\\{[A-Za-z0-9\\._]+\\}");
239         for (final PlexusConfiguration cfg : configuration.getChildren()) {
240             if (cfg.getName().equals(SERVER_TYPE_ATTRIBUTE)) {
241                 if (pattern.matcher(cfg.getValue()).matches()) {
242                     return DEFAULT_SERVER_TYPE_VALUE;
243                 } else {
244                     return cfg.getValue();
245                 }
246             }
247         }
248         return DEFAULT_SERVER_TYPE_VALUE;
249     }
250 }