001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package org.basepom.mojo.propertyhelper; 016 017import static com.google.common.base.Preconditions.checkState; 018import static java.lang.String.format; 019import static org.basepom.mojo.propertyhelper.IgnoreWarnFail.checkIgnoreWarnFailState; 020 021import org.basepom.mojo.propertyhelper.definitions.DateDefinition; 022import org.basepom.mojo.propertyhelper.definitions.FieldDefinition; 023import org.basepom.mojo.propertyhelper.definitions.MacroDefinition; 024import org.basepom.mojo.propertyhelper.definitions.NumberDefinition; 025import org.basepom.mojo.propertyhelper.definitions.PropertyGroupDefinition; 026import org.basepom.mojo.propertyhelper.definitions.StringDefinition; 027import org.basepom.mojo.propertyhelper.definitions.UuidDefinition; 028import org.basepom.mojo.propertyhelper.fields.NumberField; 029import org.basepom.mojo.propertyhelper.groups.PropertyGroup; 030import org.basepom.mojo.propertyhelper.groups.PropertyResult; 031import org.basepom.mojo.propertyhelper.macros.MacroType; 032 033import java.io.File; 034import java.io.IOException; 035import java.util.Arrays; 036import java.util.LinkedHashSet; 037import java.util.List; 038import java.util.Map; 039import java.util.Properties; 040import java.util.Set; 041import javax.inject.Inject; 042 043import com.google.common.collect.ImmutableList; 044import com.google.common.collect.ImmutableMap; 045import com.google.common.collect.ImmutableSet; 046import com.google.common.flogger.FluentLogger; 047import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 048import org.apache.maven.plugin.AbstractMojo; 049import org.apache.maven.plugin.MojoExecutionException; 050import org.apache.maven.plugins.annotations.Parameter; 051import org.apache.maven.project.MavenProject; 052import org.apache.maven.settings.Settings; 053 054public abstract class AbstractPropertyHelperMojo extends AbstractMojo implements FieldContext { 055 056 private static final FluentLogger LOG = FluentLogger.forEnclosingClass(); 057 058 protected final ValueCache valueCache = new ValueCache(); 059 private IgnoreWarnFail onDuplicateField = IgnoreWarnFail.FAIL; 060 061 /** 062 * Defines the action to take if a field is defined multiple times (e.g. as a number and a string). 063 * <br> 064 * Options are 065 * <ul> 066 * <li><code>ignore</code> - ignore multiple definitions silently, retain just the first one found</li> 067 * <li><code>warn</code> - like ignore, but also log a warning message</li> 068 * <li><code>fail</code> - fail the build with an exception</li> 069 * </ul> 070 */ 071 @Parameter(defaultValue = "fail", alias = "onDuplicateProperty") 072 public void setOnDuplicateField(String onDuplicateField) { 073 this.onDuplicateField = IgnoreWarnFail.forString(onDuplicateField); 074 } 075 076 private List<String> activeGroups = List.of(); 077 078 /** 079 * The property groups to activate. If none are given, all property groups are activated. 080 * <pre>{@code 081 * <activeGroups> 082 * <activeGroup>group1</activeGroup> 083 * <activeGroup>group2</activeGroup> 084 * ... 085 * </activeGroups> 086 * }</pre> 087 */ 088 @Parameter 089 public void setActiveGroups(String... activeGroups) { 090 this.activeGroups = Arrays.asList(activeGroups); 091 } 092 093 /** 094 * Define property groups. A property group contains one or more property definitions. Property groups are active by default 095 * unless they are explicitly listed with {@code <activeGroups>...</activeGroups}. 096 * <pre>{@code 097 * <propertyGroups> 098 * <propertyGroup> 099 * <id>...</id> 100 * 101 * <activeOnRelease>true|false</activeOnRelease> 102 * <activeOnSnapshot>true|false</activeOnSnapshot> 103 * <onDuplicateProperty>ignore|warn|fail</onDuplicateProperty> 104 * <onMissingField>ignore|warn|fail</onMissingField> 105 * 106 * <properties> 107 * <property> 108 * <name>...</name> 109 * <value>...</value> 110 * <transformers>...</transformers> 111 * </property> 112 * ... 113 * </properties> 114 * </propertyGroup> 115 * ... 116 * </propertyGroups> 117 * }</pre> 118 */ 119 @Parameter 120 public void setPropertyGroups(PropertyGroupDefinition... propertyGroups) { 121 this.propertyGroupDefinitions = Arrays.asList(propertyGroups); 122 } 123 124 private List<PropertyGroupDefinition> propertyGroupDefinitions = List.of(); 125 126 /** 127 * Number property definitions. 128 * 129 * <pre>{@code 130 * <numbers> 131 * <number> 132 * <id>...</id> 133 * <skip>true|false</skip> 134 * <export>true|false</export> 135 * 136 * <fieldNumber>...</fieldNumber> 137 * <increment>...</increment> 138 * <format>...</format> 139 * <regexp>...</regexp> 140 * <transformers>...</transformers> 141 * 142 * <propertyFile>...</propertyFile> 143 * <propertyNameInFile>...</propertyNameInFile> 144 * <initialValue>...</initialValue> 145 * <onMissingFile>ignore|warn|fail|create</onMissingFile> 146 * <onMissingFileProperty>ignore|warn|fail|create</onMissingFileProperty> 147 * <onMissingProperty>ignore|warn|fail</onMissingProperty> 148 * </number> 149 * ... 150 * </numbers> 151 * }</pre> 152 */ 153 @Parameter 154 public void setNumbers(NumberDefinition... numberDefinitions) { 155 this.numberDefinitions = Arrays.asList(numberDefinitions); 156 } 157 158 private List<NumberDefinition> numberDefinitions = List.of(); 159 160 /** 161 * String property definitions. 162 * 163 * <pre>{@code 164 * <strings> 165 * <string> 166 * <id>...</id> 167 * <skip>true|false</skip> 168 * <export>true|false</export> 169 * 170 * <values> 171 * <value>...</value> 172 * ... 173 * </values> 174 * <blankIsValid>true|false</blankIsValid> 175 * <onMissingValue>ignore|warn|fail</onMissingValue 176 * <format>...</format> 177 * <regexp>...</regexp> 178 * <transformers>...</transformers> 179 * 180 * <propertyFile>...</propertyFile> 181 * <propertyNameInFile>...</propertyNameInFile> 182 * <initialValue>...</initialValue> 183 * <onMissingFile>ignore|warn|fail|create</onMissingFile> 184 * <onMissingFileProperty>ignore|warn|fail|create</onMissingFileProperty> 185 * <onMissingProperty>ignore|warn|fail</onMissingProperty> 186 * </string> 187 * ... 188 * </strings> 189 * }</pre> 190 */ 191 @Parameter 192 public void setStrings(StringDefinition... stringDefinitions) { 193 this.stringDefinitions = Arrays.asList(stringDefinitions); 194 } 195 196 private List<StringDefinition> stringDefinitions = List.of(); 197 198 /** 199 * Date property definitions. 200 * 201 * <pre>{@code 202 * <dates> 203 * <date> 204 * <id>...</id> 205 * <skip>true|false</skip> 206 * <export>true|false</export> 207 * 208 * <value>...</value> 209 * <timezone>...</timezone> 210 * <format>...</format> 211 * <regexp>...</regexp> 212 * <transformers>...</transformers> 213 * 214 * <propertyFile>...</propertyFile> 215 * <propertyNameInFile>...</propertyNameInFile> 216 * <initialValue>...</initialValue> 217 * <onMissingFile>ignore|warn|fail|create</onMissingFile> 218 * <onMissingFileProperty>ignore|warn|fail|create</onMissingFileProperty> 219 * <onMissingProperty>ignore|warn|fail</onMissingProperty> 220 * </date> 221 * ... 222 * </dates> 223 * }</pre> 224 */ 225 @Parameter 226 public void setDates(DateDefinition... dateDefinitions) { 227 this.dateDefinitions = Arrays.asList(dateDefinitions); 228 } 229 230 private List<DateDefinition> dateDefinitions = List.of(); 231 232 /** 233 * Macro definitions. 234 * 235 * <pre>{@code 236 * <macros> 237 * <macro> 238 * <id>...</id> 239 * <skip>true|false</skip> 240 * <export>true|false</export> 241 * 242 * <macroType>...</macroType> 243 * <macroClass>...</macroClass> 244 * <properties> 245 * <some-name>some-value</some-name> 246 * ... 247 * </properties> 248 * 249 * <format>...</format> 250 * <regexp>...</regexp> 251 * <transformers>...</transformers> 252 * 253 * <propertyFile>...</propertyFile> 254 * <propertyNameInFile>...</propertyNameInFile> 255 * <initialValue>...</initialValue> 256 * <onMissingFile>ignore|warn|fail|create</onMissingFile> 257 * <onMissingFileProperty>ignore|warn|fail|create</onMissingFileProperty> 258 * <onMissingProperty>ignore|warn|fail</onMissingProperty> 259 * </macro> 260 * ... 261 * </macros> 262 * }</pre> 263 */ 264 @Parameter 265 public void setMacros(MacroDefinition... macroDefinitions) { 266 this.macroDefinitions = Arrays.asList(macroDefinitions); 267 } 268 269 private List<MacroDefinition> macroDefinitions = List.of(); 270 271 /** 272 * Uuid definitions. 273 * 274 * <pre>{@code 275 * <uuids> 276 * <uuid> 277 * <id>...</id> 278 * <skip>true|false</skip> 279 * <export>true|false</export> 280 * 281 * <value>...</value> 282 * <format>...</format> 283 * <regexp>...</regexp> 284 * <transformers>...</transformers> 285 * 286 * <propertyFile>...</propertyFile> 287 * <propertyNameInFile>...</propertyNameInFile> 288 * <initialValue>...</initialValue> 289 * <onMissingFile>ignore|warn|fail|create</onMissingFile> 290 * <onMissingFileProperty>ignore|warn|fail|create</onMissingFileProperty> 291 * <onMissingProperty>ignore|warn|fail</onMissingProperty> 292 * </uuid> 293 * ... 294 * </uuids> 295 * }</pre> 296 */ 297 @Parameter 298 public void setUuids(UuidDefinition... uuidDefinitions) { 299 this.uuidDefinitions = Arrays.asList(uuidDefinitions); 300 } 301 302 private List<UuidDefinition> uuidDefinitions = List.of(); 303 304 /** 305 * If set to true, goal execution is skipped. 306 */ 307 @Parameter(defaultValue = "false") 308 boolean skip; 309 310 /** 311 * The maven project (effective pom). 312 */ 313 @Parameter(defaultValue = "${project}", readonly = true) 314 MavenProject project; 315 316 @Parameter(defaultValue = "${settings}", readonly = true) 317 Settings settings; 318 319 @Parameter(required = true, readonly = true, defaultValue = "${project.basedir}") 320 File basedir; 321 322 @Inject 323 public void setMacroMap(Map<String, MacroType> macroMap) { 324 this.macroMap = ImmutableMap.copyOf(macroMap); 325 } 326 327 private Map<String, MacroType> macroMap = Map.of(); 328 329 // internal mojo state. 330 private boolean isSnapshot; 331 private InterpolatorFactory interpolatorFactory; 332 private TransformerRegistry transformerRegistry; 333 334 private Map<String, FieldDefinition<?>> fieldDefinitions = Map.of(); 335 private List<NumberField> numberFields = List.of(); 336 private Map<String, String> values = Map.of(); 337 338 @Override 339 public void execute() throws MojoExecutionException { 340 this.isSnapshot = project.getArtifact().isSnapshot(); 341 this.interpolatorFactory = new InterpolatorFactory(project.getModel()); 342 this.transformerRegistry = TransformerRegistry.INSTANCE; 343 344 LOG.atFine().log("Current build is a %s project.", isSnapshot ? "snapshot" : "release"); 345 LOG.atFiner().log("On duplicate field definitions: %s", onDuplicateField); 346 347 try { 348 if (skip) { 349 LOG.atFine().log("skipping plugin execution!"); 350 } else { 351 doExecute(); 352 } 353 } catch (IOException e) { 354 throw new MojoExecutionException("While running mojo: ", e); 355 } 356 } 357 358 @Override 359 public MavenProject getProject() { 360 return project; 361 } 362 363 @Override 364 public File getBasedir() { 365 return basedir; 366 } 367 368 @Override 369 public Settings getSettings() { 370 return settings; 371 } 372 373 @Override 374 public Map<String, MacroType> getMacros() { 375 return macroMap; 376 } 377 378 @Override 379 public Properties getProjectProperties() { 380 return project.getProperties(); 381 } 382 383 @Override 384 public InterpolatorFactory getInterpolatorFactory() { 385 return interpolatorFactory; 386 } 387 388 @Override 389 public TransformerRegistry getTransformerRegistry() { 390 return transformerRegistry; 391 } 392 393 protected List<NumberField> getNumbers() { 394 return numberFields; 395 } 396 397 /** 398 * Subclasses need to implement this method. 399 */ 400 protected abstract void doExecute() throws IOException, MojoExecutionException; 401 402 private void addDefinitions(ImmutableMap.Builder<String, FieldDefinition<?>> builder, List<? extends FieldDefinition<?>> newDefinitions) { 403 Map<String, FieldDefinition<?>> existingDefinitions = builder.build(); 404 405 for (FieldDefinition<?> definition : newDefinitions) { 406 if (definition.isSkip()) { 407 continue; 408 } 409 410 String propertyName = definition.getId(); 411 412 if (checkIgnoreWarnFailState(!existingDefinitions.containsKey(propertyName), onDuplicateField, 413 () -> format("field definition '%s' does not exist", propertyName), 414 () -> format("field definition '%s' already exists!", propertyName))) { 415 builder.put(propertyName, definition); 416 } 417 } 418 } 419 420 protected void createFieldDefinitions() { 421 422 ImmutableMap.Builder<String, FieldDefinition<?>> builder = ImmutableMap.builder(); 423 addDefinitions(builder, numberDefinitions); 424 addDefinitions(builder, stringDefinitions); 425 addDefinitions(builder, macroDefinitions); 426 addDefinitions(builder, dateDefinitions); 427 addDefinitions(builder, uuidDefinitions); 428 429 this.fieldDefinitions = builder.build(); 430 } 431 432 protected void createFields() throws MojoExecutionException, IOException { 433 ImmutableList.Builder<NumberField> numberFields = ImmutableList.builder(); 434 435 var builder = ImmutableMap.<String, String>builder(); 436 437 for (FieldDefinition<?> definition : fieldDefinitions.values()) { 438 Field<?, ?> field = definition.createField(this, valueCache); 439 440 if (field instanceof NumberField) { 441 numberFields.add((NumberField) field); 442 } 443 444 var fieldValue = field.getValue(); 445 builder.put(field.getFieldName(), fieldValue); 446 447 if (field.isExposeAsProperty()) { 448 project.getProperties().setProperty(field.getFieldName(), fieldValue); 449 LOG.atFine().log("Exporting Property name: %s, value: %s", field.getFieldName(), fieldValue); 450 } else { 451 LOG.atFine().log("Property name: %s, value: %s", field.getFieldName(), fieldValue); 452 } 453 } 454 455 this.numberFields = numberFields.build(); 456 this.values = builder.build(); 457 } 458 459 // generates the property groups. 460 @SuppressFBWarnings(value = "WMI_WRONG_MAP_ITERATOR") 461 public void createGroups() { 462 var groupMapBuilder = ImmutableMap.<String, PropertyGroup>builder(); 463 var resultMapBuilder = ImmutableMap.<String, Set<PropertyResult>>builder(); 464 465 Set<String> exportedFields = fieldDefinitions.values().stream() 466 .filter(FieldDefinition::isExport) 467 .map(FieldDefinition::getId).collect(ImmutableSet.toImmutableSet()); 468 469 Set<String> propertyNames = new LinkedHashSet<>(exportedFields); 470 471 propertyGroupDefinitions.forEach(propertyGroupDefinition -> { 472 PropertyGroup propertyGroup = propertyGroupDefinition.createGroup(this); 473 Set<PropertyResult> propertyResults = propertyGroup.createProperties(values); 474 groupMapBuilder.put(propertyGroup.getId(), propertyGroup); 475 resultMapBuilder.put(propertyGroup.getId(), propertyResults); 476 }); 477 478 var groupMap = groupMapBuilder.build(); 479 var resultMap = resultMapBuilder.build(); 480 481 var groupsToAdd = this.activeGroups == null 482 ? groupMap.keySet() 483 : this.activeGroups; 484 485 for (String groupToAdd : groupsToAdd) { 486 487 var activeGroup = groupMap.get(groupToAdd); 488 checkState(activeGroup != null, "activated group '%s' does not exist", groupToAdd); 489 var activeResult = resultMap.get(groupToAdd); 490 checkState(activeResult != null, "activated group '%s' has no result", groupToAdd); 491 492 if (activeGroup.checkActive(isSnapshot)) { 493 for (PropertyResult propertyResult : activeResult) { 494 String propertyName = propertyResult.getPropertyName(); 495 496 if (checkIgnoreWarnFailState(!propertyNames.contains(propertyName), activeGroup.getOnDuplicateProperty(), 497 () -> format("property '%s' is not exposed", propertyName), 498 () -> format("property '%s' is already exposed!", propertyName))) { 499 500 project.getProperties().setProperty(propertyName, propertyResult.getPropertyValue()); 501 } 502 } 503 } else { 504 LOG.atFine().log("Skipping property group %s, not active", activeGroup); 505 } 506 } 507 } 508}