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 }