1 /* 2 * Copyright 2008-2010 smartics, Kronseder & Reiner GmbH 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 de.smartics.maven.issues; 18 19 import java.io.File; 20 import java.util.List; 21 import java.util.Locale; 22 23 import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; 24 import org.apache.maven.plugin.logging.Log; 25 import org.apache.maven.reporting.MavenReportException; 26 import org.apache.maven.reporting.MavenReportRenderer; 27 import org.codehaus.plexus.util.StringUtils; 28 import org.eclipse.mylyn.tasks.core.data.TaskData; 29 30 import de.smartics.maven.issues.cache.TaskDataCache; 31 32 /** 33 * The issues Mojo reports on issues. 34 * 35 * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a> 36 * @version $Revision:591 $ 37 */ 38 public abstract class AbstractIssuesConnectionMojo extends AbstractReportMojo 39 { 40 // ********************************* Fields ********************************* 41 42 // --- constants ------------------------------------------------------------ 43 44 // --- members -------------------------------------------------------------- 45 46 /** 47 * In offline mode the plugin will not generate a report. 48 * 49 * @parameter default-value="${settings.offline}" 50 * @required 51 * @readonly 52 * @since 1.0 53 */ 54 protected boolean offline; 55 56 /** 57 * A simple flag to skip the generation of the reports. If set on the command 58 * line use <code>-Dissues.skip</code>. 59 * 60 * @parameter expression="${issues.skip}" default-value="false" 61 * @since 1.0 62 */ 63 protected boolean skip; 64 65 /** 66 * List of report output names to be skipped. This is useful to remove the 67 * generation of a particular report without removing the description. This 68 * configuration can also be used to remove configured reports in a parent's 69 * POM. 70 * 71 * @parameter 72 * @since 1.0 73 */ 74 private List<String> skipReports; 75 76 // ... version .............................................................. 77 78 /** 79 * The name of the class that creates comparable version instances. The class 80 * specified here has to implement 81 * {@link de.smartics.maven.issues.version.VersionFactory}. 82 * 83 * @required 84 * @parameter 85 * default-value="de.smartics.maven.issues.bugzilla.BugzillaVersionFactory" 86 * @since 1.0 87 */ 88 private String versionFactory; 89 90 /** 91 * The range of version to be included in the search. Use <code>[]</code> to 92 * specify inclusion and <code>()</code> for exclusion of the bounds. 93 * <p> 94 * Examples: 95 * </p> 96 * <ul> 97 * <li><code>1.0</code> Version 1.0</li> 98 * <li><code>[1.0,2.0)</code> Versions 1.0 (included) to 2.0 (not included)</li> 99 * <li><code>[1.0,2.0]</code> Versions 1.0 to 2.0 (both included)</li> 100 * <li><code>[1.5,)</code> Versions 1.5 and higher</li> 101 * <li><code>(,1.0],[1.2,)</code> Versions up to 1.0 (included) and 1.2 or 102 * higher</li> 103 * </ul> 104 * <p> 105 * If this value is specified the property 106 * {@link #isCurrentReleaseVersionOnly()} is overridden. 107 * </p> 108 * <p> 109 * It is required that the first specified restriction has at least one 110 * inclusive bound or is starting from version 0.0.0. 111 * </p> 112 * <ul> 113 * <li> 114 * <li><code>(,1.0)</code> is correct. 115 * <li><code>[0.5.0,1.0)</code> is correct. 116 * <li><code>(0.5.0,1.0]</code> is correct. 117 * <li><code>(0.5.0,1.0)</code> is <em>not</em> correct. 118 * </ul> 119 * 120 * @parameter 121 * @since 1.0 122 */ 123 private String versionRange; 124 125 // ... issue management infrastructure ...................................... 126 127 /** 128 * The directory to store task data information for the task cache. This is an 129 * optimization to not fetch tasks already fetched. 130 * 131 * @parameter expression="${project.build.directory}/task-data" 132 * @required 133 * @readonly 134 * @since 1.0 135 */ 136 protected String buildDirectory; 137 138 /** 139 * The directory to store task data information for the task cache. This is an 140 * optimization to not fetch tasks already fetched. 141 * 142 * @parameter expression="${issues.repositoryFacadeFactoryClass}" 143 * default-value= 144 * "de.smartics.maven.issues.factory.DefaultRepositoryFacadeFactory" 145 * @required 146 * @since 1.0 147 */ 148 protected String repositoryFacadeFactoryClass; 149 150 /** 151 * The factory creates repository facades to access the issues stored in the 152 * issue management system. 153 */ 154 protected RepositoryFacadeFactory repositoryFacadeFactory; 155 156 // ... issue management information ......................................... 157 158 /** 159 * The name of the issue management system to connect to. 160 * 161 * @parameter default-value="${project.issueManagement.system}" 162 * @since 1.0 163 */ 164 protected String issueManagementSystem; 165 166 /** 167 * The URL to the issue management system to connect to. 168 * 169 * @parameter default-value="${project.issueManagement.url}" 170 * @since 1.0 171 */ 172 protected String issueManagementUrl; 173 174 /** 175 * The name of the user for authentication to access a private installation of 176 * a issue management system. 177 * 178 * @parameter default-value="" 179 * @since 1.0 180 */ 181 protected String issueManagementUser; 182 183 /** 184 * The password for authentication to access a private installation of a issue 185 * management system. 186 * 187 * @parameter default-value="" 188 * @since 1.0 189 */ 190 protected String issueManagementPassword; 191 192 /** 193 * The name of the user for HTTP basic authentication to the issue management 194 * webserver. 195 * 196 * @parameter default-value="" 197 * @since 1.0 198 */ 199 protected String webUser; 200 201 /** 202 * The password for HTTP basic authentication to the issue management 203 * webserver. 204 * 205 * @parameter default-value="" 206 * @since 1.0 207 */ 208 protected String webPassword; 209 210 /** 211 * Sets the version of the task repository. 212 * 213 * @parameter 214 * @since 1.0 215 */ 216 protected String repositoryVersion; 217 218 /** 219 * Maximum number of entries to be displayed by the issue report. Use 220 * <code>-1</code> for unlimited entries. 221 * 222 * @parameter default-value=-1 223 * @since 1.0 224 */ 225 private int maxEntries; 226 227 /** 228 * Maximum number retries to connect to the issue management system. 229 * 230 * @parameter default-value=3 231 * @since 1.0 232 */ 233 protected int maxRetries; 234 235 /** 236 * The timeout in milliseconds between retries of connection attempts. 237 * 238 * @parameter default-value=7000 239 * @since 1.0 240 */ 241 protected long timeout; 242 243 /** 244 * The flag indicates whether (<code>true</code>) or not (<code>false</code>) 245 * logout problems from the task repository should be ignored. 246 * <p> 247 * This is a kind of hack that grabs for the string 'Logout' in the exception 248 * message reported from the task repository. It may be useful if the task 249 * repository's response is quite slow. 250 * 251 * @parameter default-value="false" 252 * @since 1.0 253 */ 254 protected boolean ignoreLogoutProblem; 255 256 /** 257 * Informs that the column names retrieved by the query should be logged at 258 * the end of the query. This allows to grab the keywords returned by the 259 * issue management system. 260 * 261 * @parameter default-value="false" 262 * @since 1.0 263 */ 264 protected boolean logColumns; 265 266 // ****************************** Initializer ******************************* 267 268 // ****************************** Constructors ****************************** 269 270 // ****************************** Inner Classes ***************************** 271 272 // ********************************* Methods ******************************** 273 274 // --- init ----------------------------------------------------------------- 275 276 // --- get&set -------------------------------------------------------------- 277 278 // --- business ------------------------------------------------------------- 279 280 /** 281 * {@inheritDoc} 282 * 283 * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale) 284 */ 285 @Override 286 protected void executeReport(final Locale locale) throws MavenReportException 287 { 288 super.executeReport(locale); 289 290 try 291 { 292 if (repositoryFacadeFactory == null) 293 { 294 repositoryFacadeFactory = create(repositoryFacadeFactoryClass); 295 } 296 297 final IssueManagementConfig config = createIssueManagementConfig(); 298 prepareResources(); 299 300 final VersionFactory versionFactoryInstance = 301 createInstance(versionFactory); 302 final RepositoryFacade facade = repositoryFacadeFactory.create(config); 303 final ArtifactVersionRange versionRange = 304 createVersionRange(versionFactoryInstance); 305 final QueryData queryData = createQueryData(versionRange); 306 final List<TaskData> issues = facade.queryTasks(queryData); 307 final MavenReportRenderer renderer = 308 createRenderer(locale, versionFactoryInstance, versionRange, issues); 309 renderer.render(); 310 } 311 catch (final Exception e) 312 { 313 final String message = "Cannot generate " + getName(locale) + "."; 314 if (getLog().isWarnEnabled()) 315 { 316 getLog().warn(message, e); 317 } 318 throw new MavenReportException(message, e); 319 } 320 } 321 322 protected ArtifactVersionRange createVersionRange( 323 final VersionFactory versionFactoryInstance) 324 { 325 if (versionRange != null) 326 { 327 try 328 { 329 return versionFactoryInstance.createRange(versionRange); 330 } 331 catch (final InvalidVersionSpecificationException e) 332 { 333 throw new IllegalArgumentException("Cannot parse version range '" 334 + versionRange + "'.", e); 335 } 336 } 337 else 338 { 339 return null; 340 } 341 } 342 343 /** 344 * Creates the query data that enables to create a query URL to be sent to the 345 * issue management system. 346 * 347 * @param versionRange the version range used to create the query data. 348 * @return an instance of query data suitable to run in a specific issue 349 * management system. 350 */ 351 protected abstract QueryData createQueryData(ArtifactVersionRange versionRange); 352 353 /** 354 * Creates a report neutral configuration that provides information to access 355 * an issue management system. 356 * 357 * @return a valid configuration instance. 358 * @throws IllegalArgumentException if the configuration cannot be build due 359 * to missing or invalid information. 360 */ 361 private IssueManagementConfig createIssueManagementConfig() 362 throws IllegalArgumentException 363 { 364 normalizeConfigurationParameters(); 365 366 final IssueManagementConfig config = 367 new IssueManagementConfig(issueManagementSystem, issueManagementUrl); 368 config.setIgnoreLogoutProblem(ignoreLogoutProblem); 369 config.setIssueManagementPassword(issueManagementPassword); 370 config.setIssueManagementUser(issueManagementUser); 371 config.setMaxEntries(maxEntries); 372 config.setMaxRetries(maxRetries); 373 config.setTimeout(timeout); 374 config.setRepositoryVersion(repositoryVersion); 375 config.setWebPassword(webPassword); 376 config.setWebUser(webUser); 377 config.setBuildDirectory(buildDirectory); 378 config.setLogColumns(logColumns); 379 return config; 380 } 381 382 /** 383 * Prepares the static resources if not already prepared. 384 * 385 * @throws MavenReportException if the resource cannot be prepared. 386 */ 387 private void prepareResources() throws MavenReportException 388 { 389 final File taskDataDir = new File(buildDirectory); 390 if (!taskDataDir.exists()) 391 { 392 final boolean created = taskDataDir.mkdirs(); 393 if (!created) 394 { 395 throw new MavenReportException( 396 "Cannot create task data directory to store seralized task data."); 397 } 398 } 399 400 TaskDataCache cache = ResourceLocator.getInstance().getTaskDataCache(); 401 if (cache == null) 402 { 403 cache = new TaskDataCache(taskDataDir); 404 ResourceLocator.getInstance().setTaskDataCacheIfUnset(cache); 405 } 406 } 407 408 /** 409 * Creates a renderer. 410 * 411 * @param locale the locale to use for the report. 412 * @param issues the issues to render. 413 * @return the renderer ready to run. 414 * @throws MavenReportException if the renderer cannot be created or 415 * configured. 416 */ 417 protected abstract MavenReportRenderer createRenderer(Locale locale, 418 VersionFactory versionFactoryInstance, ArtifactVersionRange versionRange, 419 List<TaskData> issues) throws MavenReportException; 420 421 /** 422 * Normalizes configuration parameters to ensure a successful run of the 423 * report. 424 */ 425 private void normalizeConfigurationParameters() 426 { 427 if (issueManagementUrl.endsWith("/")) 428 { 429 issueManagementUrl = StringUtils.chop(issueManagementUrl); 430 } 431 } 432 433 /** 434 * Checks if all required information is provided to run the report 435 * successfully. If the plugin is running in offline mode, this method returns 436 * <code>false</code>. 437 * 438 * @return <code>true</code> if a report can be rendered, <code>false</code> 439 * otherwise. 440 * @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport() 441 */ 442 @Override 443 public boolean canGenerateReport() 444 { 445 if (offline || skip || skipReport()) 446 { 447 return false; 448 } 449 450 repositoryFacadeFactory = create(repositoryFacadeFactoryClass); 451 return validateIssueManagementConfiguration(); 452 } 453 454 private boolean skipReport() 455 { 456 final String reportId = getOutputName(); 457 return this.skipReports != null && skipReports.contains(reportId); 458 } 459 460 /** 461 * Creates the factory with the given class name. 462 * 463 * @param repositoryFacadeFactoryClassName the name of the factory to create. 464 * @return the factory of the given name. 465 * @throws IllegalArgumentException if the class cannot be instantiated. 466 */ 467 @SuppressWarnings("unchecked") 468 private RepositoryFacadeFactory create( 469 final String repositoryFacadeFactoryClassName) 470 throws IllegalArgumentException 471 { 472 try 473 { 474 final Class<RepositoryFacadeFactory> clazz = 475 (Class<RepositoryFacadeFactory>) Class 476 .forName(repositoryFacadeFactoryClassName); 477 final RepositoryFacadeFactory factory = clazz.newInstance(); 478 return factory; 479 } 480 catch (final Exception e) 481 { 482 throw new IllegalArgumentException("Cannot create factory class '" 483 + repositoryFacadeFactoryClassName 484 + "'.", e); 485 } 486 } 487 488 /** 489 * Checks whether all required issue management configuration information is 490 * set. 491 * <p> 492 * The checking includes 493 * </p> 494 * <ul> 495 * <li>Is the URL to the issue management server provided?</li> 496 * <li>Is the name of the issue management system provided?</li> 497 * <li>Is the issue management server supported.</li> 498 * </ul> 499 * 500 * @return <code>true</code> if all required issue management information is 501 * provided and valid, <code>false</code> otherwise. 502 */ 503 private boolean validateIssueManagementConfiguration() 504 { 505 if (StringUtils.isBlank(issueManagementUrl)) 506 { 507 getLog().error("URL to issue management system missing."); 508 return false; 509 } 510 else if (StringUtils.isBlank(issueManagementSystem)) 511 { 512 getLog().error("Name of issue management system missing."); 513 return false; 514 } 515 else if (!repositoryFacadeFactory.supports(issueManagementSystem)) 516 { 517 getLog().error( 518 "The issue report does not support '" + issueManagementSystem + "'."); 519 return false; 520 } 521 522 return true; 523 } 524 525 /** 526 * Creates the instance of a class expecting a no-args constructor. 527 * 528 * @param <T> the type of the instance to be created. 529 * @param className the name of the class to create an instance of. 530 * @return the created instance. 531 * @throws IllegalArgumentException if a problem prevents the creation of the 532 * instance. 533 */ 534 @SuppressWarnings("unchecked") 535 protected <T> T createInstance(final String className) 536 throws IllegalArgumentException 537 { 538 try 539 { 540 final Class<T> clazz = (Class<T>) Class.forName(className); 541 final T instance = clazz.newInstance(); 542 return instance; 543 } 544 catch (final Exception e) 545 { 546 final String message = 547 "Cannot create instance for class '" + className + "'."; 548 final Log log = getLog(); 549 if (log.isWarnEnabled()) 550 { 551 log.warn(message, e); 552 } 553 throw new IllegalArgumentException(message, e); 554 } 555 } 556 557 // --- object basics -------------------------------------------------------- 558 559 }