001    /**
002     * Copyright 2004-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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    package org.kuali.maven.common;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.Collections;
021    import java.util.List;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.apache.maven.project.MavenProject;
025    import org.slf4j.Logger;
026    import org.slf4j.LoggerFactory;
027    
028    /**
029     * Utility class for working with URL's related to Maven web site generation
030     */
031    public class UrlBuilder {
032            private static final Logger logger = LoggerFactory.getLogger(UrlBuilder.class);
033    
034            /**
035             * Return the fully qualified url for publishing the web site
036             */
037            public String getPublishUrl(MavenProject project, SiteContext context) {
038                    return getSiteUrl(project, context, context.getPublishBase());
039            }
040    
041            /**
042             * Return the fully qualified url for public access to the web site
043             */
044            public String getPublicUrl(MavenProject project, SiteContext context) {
045                    return getSiteUrl(project, context, context.getPublicBase());
046            }
047    
048            /**
049             * Return true if the groupId of the project positively matches the groupId passed in. False otherwise.
050             */
051            protected boolean isTargetGroupId(MavenProject project, String groupId) {
052                    // Return false if we can't do a meaningful comparison
053                    if (project == null || groupId == null) {
054                            return false;
055                    }
056    
057                    // Check that it matches our target groupId
058                    return groupId.equals(project.getGroupId());
059            }
060    
061            /**
062             * Return the non-redundant portion of the group id. Given a project with a groupId of "org.kuali.maven.plugins" and an
063             * organizationGroupId of "org.kuali" return "maven.plugins"
064             */
065            protected String getTrimmedGroupId(MavenProject project, String organizationGroupId) {
066                    String groupId = project.getGroupId();
067    
068                    // Return the original group id if no organization group id is provided
069                    if (StringUtils.isEmpty(organizationGroupId)) {
070                            logger.warn("No organization group id to compare with");
071                            return groupId;
072                    }
073    
074                    // Return the original group id if it is not a Kuali project
075                    if (!groupId.startsWith(organizationGroupId)) {
076                            logger.warn("Group Id: '" + groupId + "' does not start with '" + organizationGroupId + "'");
077                            return groupId;
078                    } else {
079                            // Remove the redundant portion of the group id
080                            String trimmed = StringUtils.replace(groupId, organizationGroupId, "");
081    
082                            // Remove the leading dot, if necessary
083                            if (trimmed.startsWith(".")) {
084                                    trimmed = trimmed.substring(1);
085                            }
086    
087                            // Return the trimmed group id
088                            return trimmed;
089                    }
090            }
091    
092            /**
093             * Replace any dots with backslashes for the groupId passed in.
094             */
095            protected String getGroupIdPath(String groupId) {
096                    return groupId.replace(".", "/");
097            }
098    
099            /**
100             * Return true if the project has no parent, OR is one of the official org POM's. False otherwise.
101             */
102            protected boolean isTopLevelProject(MavenProject project, List<MavenProject> orgPoms) {
103                    MavenProject parent = project.getParent();
104                    if (parent == null) {
105                            return true;
106                    } else {
107                            return isMatch(parent, orgPoms);
108                    }
109            }
110    
111            /**
112             * Return true if groupId + artifactId of "project" matches a groupId + artifactId from "targetProjects"
113             */
114            protected boolean isMatch(MavenProject project, List<MavenProject> targetProjects) {
115                    for (MavenProject targetProject : targetProjects) {
116                            if (isMatch(project, targetProject)) {
117                                    return true;
118                            }
119                    }
120                    return false;
121            }
122    
123            /**
124             * Return true if the groupId's and artifactId's match
125             */
126            protected boolean isMatch(MavenProject project1, MavenProject project2) {
127                    // Pull out the groupIds
128                    String groupId1 = project1.getGroupId();
129                    String groupId2 = project2.getGroupId();
130    
131                    // If the groupIds don't match, we are done
132                    if (!groupId1.equals(groupId2)) {
133                            return false;
134                    }
135    
136                    // If we get here the groupId's match
137                    String artifactId1 = project1.getArtifactId();
138                    String artifactId2 = project2.getArtifactId();
139    
140                    // Return true only if the artifactIds also match
141                    return artifactId1.equals(artifactId2);
142            }
143    
144            /**
145             * Return true if the artifact id should be appended to the url for site publication.
146             *
147             * This method returns true, unless we have a multi-module project where the top level project's artifact id matches the final portion
148             * of the groupId.
149             *
150             * For example, the Kuali Student top level pom has the groupId "org.kuali.student" and the artifactId "student". We want to translate
151             * that groupId + artifactId into "site.kuali.org/student" NOT "site.kuali.org/student/student"
152             */
153            protected boolean isAppendArtifactId(MavenProject project, String trimmedGroupId) {
154                    // Always append the artifactId for single module projects
155                    if (isEmpty(project.getModules())) {
156                            return true;
157                    }
158    
159                    // Always append the artifactId if it is different than the final
160                    // portion of the groupId
161                    if (!trimmedGroupId.endsWith(project.getArtifactId())) {
162                            return true;
163                    }
164    
165                    /**
166                     * We have a multi-module build where the artifactId for the top level project is the same as the final portion of the groupId.<br>
167                     * eg org.kuali.rice:rice Return false here so the public url is:<br>
168                     * http://site.kuali.org/rice/1.0.3<br>
169                     * instead of<br>
170                     * http://site.kuali.org/rice/rice/1.0.3<br>
171                     */
172                    return false;
173            }
174    
175            /**
176             * Return a fully qualified url
177             */
178            protected String getSiteUrl(MavenProject project, SiteContext context, String urlBase) {
179                    StringBuilder sb = new StringBuilder();
180    
181                    // append the base we've been given
182                    sb.append(urlBase);
183    
184                    // Add a slash, if necessary
185                    if (!context.getPublishBase().endsWith("/")) {
186                            sb.append("/");
187                    }
188    
189                    // Append the path for this project
190                    sb.append(getSitePath(project, context));
191    
192                    // Return the fully qualified url
193                    return sb.toString();
194            }
195    
196            /**
197             * Return the portion of the url to the right of the hostname
198             */
199            public String getSitePath(MavenProject project, SiteContext context) {
200                    // Convert the project hierarchy into appropriate url tokens
201                    List<String> tokens = getUrlTokens(project, context);
202    
203                    StringBuilder sb = new StringBuilder();
204                    // Append the tokens
205                    for (String token : tokens) {
206                            sb.append(token);
207                            sb.append("/");
208                    }
209                    return sb.toString();
210            }
211    
212            /**
213             * Return a list of tokens representing url paths for this project
214             */
215            protected List<String> getUrlTokens(MavenProject project, SiteContext context) {
216                    // Determine if the project they passed in is a top level pom
217                    boolean orgPom = isMatch(project, context.getOrgPoms());
218                    if (orgPom) {
219                            // If so, just return top level tokens
220                            return getTopLevelTokens(project, context);
221                    } else {
222                            // Otherwise, examine the full project structure
223                            List<MavenProject> projectPath = getProjectPath(project);
224    
225                            // Convert the project structure into url tokens
226                            return getUrlTokens(projectPath, context);
227                    }
228            }
229    
230            /**
231             * Return appropriate url tokens for the list of projects representing the path of projects from the highest level pom to our current
232             * project
233             */
234            protected List<String> getUrlTokens(List<MavenProject> projects, SiteContext context) {
235                    List<String> tokens = new ArrayList<String>();
236                    for (MavenProject project : projects) {
237                            // If it is an org pom, skip it, since they are not needed
238                            // for calculating the url used for the site path
239                            boolean orgPom = isMatch(project, context.getOrgPoms());
240                            if (!orgPom) {
241                                    // Add tokens appropriate for this project
242                                    addProjectTokens(project, context, tokens);
243                            }
244                    }
245                    // return our list of tokens
246                    return tokens;
247            }
248    
249            /**
250             * Add appropriate values to the list of tokens
251             */
252            protected void addProjectTokens(MavenProject project, SiteContext context, List<String> tokens) {
253                    // Is this a top level project?
254                    boolean topLevelProject = isTopLevelProject(project, context.getOrgPoms());
255                    if (topLevelProject) {
256                            // If so, add the top level tokens (groupId, artifactId, version)
257                            tokens.addAll(getTopLevelTokens(project, context));
258                    } else {
259                            // Otherwise just add the artifactId
260                            tokens.add(project.getArtifactId());
261                    }
262            }
263    
264            /**
265             * Return the appropriate tokens for this top level project
266             */
267            protected List<String> getTopLevelTokens(MavenProject project, SiteContext context) {
268                    List<String> tokens = new ArrayList<String>();
269    
270                    // Trim off the redundant portion of the group id
271                    String trimmedGroupId = getTrimmedGroupId(project, context.getOrganizationGroupId());
272    
273                    // Convert dots to slashes, and add to our list of tokens
274                    if (trimmedGroupId.length() > 0) {
275                            tokens.add(getGroupIdPath(trimmedGroupId));
276                    }
277    
278                    // Only time we don't append the artifact id is when it matches the final portion of the group id
279                    boolean appendArtifactId = isAppendArtifactId(project, trimmedGroupId);
280                    if (appendArtifactId) {
281                            tokens.add(project.getArtifactId());
282                    }
283    
284                    // Always add either the version or "latest"
285                    if (context.isLatest()) {
286                            tokens.add(context.getLatestToken());
287                    } else {
288                            tokens.add(project.getVersion());
289                    }
290    
291                    // Return what we've found
292                    return tokens;
293            }
294    
295            /**
296             * Return a List representing the complete hierarchy for this project.
297             *
298             * The list is ordered where the top level pom is first, and the current project is last
299             */
300            protected List<MavenProject> getProjectPath(MavenProject project) {
301                    List<MavenProject> projects = new ArrayList<MavenProject>();
302                    buildPath(project, projects);
303                    Collections.reverse(projects);
304                    return projects;
305            }
306    
307            /**
308             * Traverse the hierarchy of projects and flatten it into a List
309             */
310            protected void buildPath(MavenProject project, List<MavenProject> projects) {
311                    projects.add(project);
312                    MavenProject parent = project.getParent();
313                    if (parent == null) {
314                            return;
315                    } else {
316                            buildPath(parent, projects);
317                    }
318            }
319    
320            /**
321             * Return the fully qualified URL for downloading a Kuali artifact from Kuali's Maven repo.
322             */
323            public String getDownloadUrl(MavenProject project, SiteContext context) {
324                    String baseUrl = context.getDownloadBase();
325                    StringBuilder sb = new StringBuilder();
326                    sb.append(baseUrl);
327                    if (!baseUrl.endsWith("/")) {
328                            sb.append("/");
329                    }
330    
331                    // "snapshot", "release", or "external"
332                    sb.append(getDownloadPath(project, context));
333                    sb.append("/");
334    
335                    // Convert dots to slashes
336                    sb.append(getGroupIdPath(project.getGroupId()));
337                    sb.append("/");
338    
339                    sb.append(project.getArtifactId());
340                    sb.append("/");
341    
342                    sb.append(project.getVersion());
343                    sb.append("/");
344                    return sb.toString();
345            }
346    
347            /**
348             * Decide between "snapshot", "release", and "external"
349             */
350            protected String getDownloadPath(MavenProject project, SiteContext context) {
351                    // All snapshots (Kuali and non-Kuali) go into the "snapshot" folder
352                    if (isSnapshot(project.getVersion(), context.getSnapshotSnippet())) {
353                            return context.getDownloadSnapshotPath();
354                    }
355    
356                    // If we get here we are dealing with a non-snapshot artifact
357    
358                    // It would be better to positively identify something as a release artifact
359                    // instead of assuming "non-snapshot" == "release"
360    
361                    if (isOrganizationProject(project, context.getOrganizationGroupId())) {
362                            // For Kuali projects, this is "release"
363                            return context.getDownloadReleasePath();
364                    } else {
365                            // For non-Kuali projects, this is "external"
366                            return context.getDownloadExternalPath();
367                    }
368            }
369    
370            /**
371             * Return true if the group id of the project matches organization group id, false otherwise.
372             */
373            protected boolean isOrganizationProject(MavenProject project, String organizationGroupId) {
374                    return project.getGroupId().startsWith(organizationGroupId);
375            }
376    
377            /**
378             * Return true if version contains snapshotSnippet (case insensitive)
379             */
380            protected boolean isSnapshot(String version, String snapshotSnippet) {
381                    return version.toUpperCase().contains(snapshotSnippet);
382            }
383    
384            /**
385             * Return true if the Collection passed in is null or size zero, false otherwise
386             */
387            protected boolean isEmpty(Collection<?> c) {
388                    if (c == null || c.isEmpty()) {
389                            return true;
390                    } else {
391                            return false;
392                    }
393            }
394    
395            /**
396             * Return true if the string contains "${", false otherwise.
397             */
398            public boolean containsUnresolvedProperty(String s) {
399                    return s.contains("${");
400            }
401    
402            /**
403             * Return true if the string is, null, empty, or contains an unresolved property
404             */
405            public boolean isUnresolved(String s) {
406                    if (StringUtils.isEmpty(s)) {
407                            return true;
408                    } else {
409                            return containsUnresolvedProperty(s);
410                    }
411            }
412    
413            /**
414             *
415             */
416            public boolean determineMatch(String generatedUrl, String mavenUrl, SiteContext context, MavenProject project) {
417                    boolean urlMatch = isUrlMatch(generatedUrl, mavenUrl);
418                    if (urlMatch) {
419                            return true;
420                    }
421                    if (!context.isLatest()) {
422                            return false;
423                    }
424                    String version = project.getVersion();
425                    if (!mavenUrl.contains(project.getVersion())) {
426                            logger.warn("Maven url does not contain the maven version number");
427                            return false;
428                    }
429                    String newMavenUrl = mavenUrl.replace(version, context.getLatestToken());
430                    return isUrlMatch(generatedUrl, newMavenUrl);
431            }
432    
433            /**
434             * Return true if the 2 urls are exactly the same or if the only thing different about them is a trailing slash
435             */
436            public boolean isUrlMatch(String generatedUrl, String mavenUrl) {
437                    if (generatedUrl.equals(mavenUrl)) {
438                            return true;
439                    }
440                    if ((generatedUrl + "/").equals(mavenUrl)) {
441                            return true;
442                    }
443                    if (generatedUrl.equals(mavenUrl + "/")) {
444                            return true;
445                    }
446                    return false;
447            }
448    
449            /**
450             * Given a list of GAV's in the form [groupId]:[artifactId]:[version] return MavenProject objects with groupId, artifactId, and version
451             * set
452             */
453            public List<MavenProject> getMavenProjects(List<String> gavs) {
454                    List<MavenProject> projects = new ArrayList<MavenProject>();
455                    if (gavs == null) {
456                            return projects;
457                    }
458                    for (String gav : gavs) {
459                            MavenProject project = getMavenProject(gav);
460                            projects.add(project);
461                    }
462                    return projects;
463            }
464    
465            /**
466             * Given a GAV in the form [groupId]:[artifactId]:[version] return a MavenProject object that has groupId, artifactId, and version set
467             */
468            public MavenProject getMavenProject(String gav) {
469                    // Split the string into tokens
470                    String[] tokens = StringUtils.splitByWholeSeparator(gav.trim(), ":");
471    
472                    // Setup some local storage
473                    String groupId = null;
474                    String artifactId = null;
475                    String version = null;
476    
477                    // Extract information from the tokens
478                    if (tokens.length > 0) {
479                            groupId = tokens[0].trim();
480                    }
481                    if (tokens.length > 1) {
482                            artifactId = tokens[1].trim();
483                    }
484                    if (tokens.length > 2) {
485                            version = tokens[2].trim();
486                    }
487    
488                    // Store info into a MavenProject
489                    MavenProject project = new MavenProject();
490                    project.setGroupId(groupId);
491                    project.setArtifactId(artifactId);
492                    project.setVersion(version);
493                    return project;
494            }
495    
496    }