001/* 002 * Copyright 2007-2021 The jdeb developers. 003 * 004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 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 017package org.vafer.jdeb.maven; 018 019import java.io.File; 020import java.io.FileInputStream; 021import java.io.FileNotFoundException; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.commons.compress.archivers.tar.TarArchiveEntry; 032import org.apache.commons.compress.archivers.tar.TarConstants; 033import org.apache.maven.artifact.Artifact; 034import org.apache.maven.execution.MavenSession; 035import org.apache.maven.plugin.AbstractMojo; 036import org.apache.maven.plugin.MojoExecutionException; 037import org.apache.maven.plugins.annotations.Component; 038import org.apache.maven.plugins.annotations.LifecyclePhase; 039import org.apache.maven.plugins.annotations.Mojo; 040import org.apache.maven.plugins.annotations.Parameter; 041import org.apache.maven.project.MavenProject; 042import org.apache.maven.project.MavenProjectHelper; 043import org.apache.maven.settings.Profile; 044import org.apache.maven.settings.Settings; 045import org.apache.tools.tar.TarEntry; 046import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; 047import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException; 048import org.vafer.jdeb.Console; 049import org.vafer.jdeb.DataConsumer; 050import org.vafer.jdeb.DataProducer; 051import org.vafer.jdeb.DebMaker; 052import org.vafer.jdeb.PackagingException; 053import org.vafer.jdeb.utils.MapVariableResolver; 054import org.vafer.jdeb.utils.SymlinkUtils; 055import org.vafer.jdeb.utils.Utils; 056import org.vafer.jdeb.utils.VariableResolver; 057 058import static org.vafer.jdeb.utils.Utils.isBlank; 059import static org.vafer.jdeb.utils.Utils.lookupIfEmpty; 060 061/** 062 * Creates Debian package 063 */ 064@Mojo(name = "jdeb", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true) 065public class DebMojo extends AbstractMojo { 066 067 @Component 068 private MavenProjectHelper projectHelper; 069 070 @Component(hint = "jdeb-sec") 071 private SecDispatcher secDispatcher; 072 073 /** 074 * Defines the name of deb package. 075 */ 076 @Parameter 077 private String name; 078 079 /** 080 * Defines the pattern of the name of final artifacts. Possible 081 * substitutions are [[baseDir]] [[buildDir]] [[artifactId]] [[version]] 082 * [[extension]] and [[groupId]]. 083 */ 084 @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]]_all.[[extension]]") 085 private String deb; 086 087 /** 088 * Explicitly defines the path to the control directory. At least the 089 * control file is mandatory. 090 */ 091 @Parameter(defaultValue = "[[baseDir]]/src/deb/control") 092 private String controlDir; 093 094 /** 095 * Explicitly define the file to read the changes from. 096 */ 097 @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt") 098 private String changesIn; 099 100 /** 101 * Explicitly define the file where to write the changes to. 102 */ 103 @Parameter(defaultValue = "[[buildDir]]/[[artifactId]]_[[version]]_all.changes") 104 private String changesOut; 105 106 /** 107 * Explicitly define the file where to write the changes of the changes input to. 108 */ 109 @Parameter(defaultValue = "[[baseDir]]/CHANGES.txt") 110 private String changesSave; 111 112 /** 113 * The compression method used for the data file (none, gzip, bzip2 or xz) 114 */ 115 @Parameter(defaultValue = "gzip") 116 private String compression; 117 118 /** 119 * Boolean option whether to attach the artifact to the project 120 */ 121 @Parameter(defaultValue = "true") 122 private String attach; 123 124 /** 125 * The location where all package files will be installed. By default, all 126 * packages are installed in /opt (see the FHS here: 127 * http://www.pathname.com/ 128 * fhs/pub/fhs-2.3.html#OPTADDONAPPLICATIONSOFTWAREPACKAGES) 129 */ 130 @Parameter(defaultValue = "/opt/[[artifactId]]") 131 private String installDir; 132 133 /** 134 * The type of attached artifact 135 */ 136 @Parameter(defaultValue = "deb") 137 private String type; 138 139 /** 140 * The project base directory 141 */ 142 @Parameter(defaultValue = "${basedir}", required = true, readonly = true) 143 private File baseDir; 144 145 /** 146 * The Maven Session Object 147 */ 148 @Parameter( defaultValue = "${session}", readonly = true ) 149 private MavenSession session; 150 151 /** 152 * The Maven Project Object 153 */ 154 @Parameter( defaultValue = "${project}", readonly = true ) 155 private MavenProject project; 156 157 /** 158 * The build directory 159 */ 160 @Parameter(property = "project.build.directory", required = true, readonly = true) 161 private File buildDirectory; 162 163 /** 164 * The classifier of attached artifact 165 */ 166 @Parameter 167 private String classifier; 168 169 /** 170 * The digest algorithm to use. 171 * 172 * @see org.bouncycastle.bcpg.HashAlgorithmTags 173 */ 174 @Parameter(defaultValue = "SHA1") 175 private String digest; 176 177 /** 178 * "data" entries used to determine which files should be added to this deb. 179 * The "data" entries may specify a tarball (tar.gz, tar.bz2, tgz), a 180 * directory, or a normal file. An entry would look something like this in 181 * your pom.xml: 182 * 183 * 184 * <pre> 185 * <build> 186 * <plugins> 187 * <plugin> 188 * <artifactId>jdeb</artifactId> 189 * <groupId>org.vafer</groupId> 190 * ... 191 * <configuration> 192 * ... 193 * <dataSet> 194 * <data> 195 * <src>${project.basedir}/target/my_archive.tar.gz</src> 196 * <include>...</include> 197 * <exclude>...</exclude> 198 * <mapper> 199 * <type>perm</type> 200 * <strip>1</strip> 201 * <prefix>/somewhere/else</prefix> 202 * <user>santbj</user> 203 * <group>santbj</group> 204 * <mode>600</mode> 205 * </mapper> 206 * </data> 207 * <data> 208 * <src>${project.build.directory}/data</src> 209 * <include></include> 210 * <exclude>**/.svn</exclude> 211 * <mapper> 212 * <type>ls</type> 213 * <src>mapping.txt</src> 214 * </mapper> 215 * </data> 216 * <data> 217 * <type>link</type> 218 * <linkName>/a/path/on/the/target/fs</linkName> 219 * <linkTarget>/a/sym/link/to/the/scr/file</linkTarget> 220 * <symlink>true</symlink> 221 * </data> 222 * <data> 223 * <src>${project.basedir}/README.txt</src> 224 * </data> 225 * </dataSet> 226 * </configuration> 227 * </plugins> 228 * </build> 229 * </pre> 230 * 231 */ 232 @Parameter 233 private Data[] dataSet; 234 235// /** 236// * @deprecated 237// */ 238// @Parameter(defaultValue = "false") 239// private boolean timestamped; 240 241 /** 242 * When enabled SNAPSHOT inside the version gets replaced with current timestamp or 243 * if set a value of a environment variable. 244 */ 245 @Parameter(defaultValue = "false") 246 private boolean snapshotExpand; 247 248 /** 249 * Which environment variable to check for the SNAPSHOT value. 250 * If the variable is not set/empty it will default to use the timestamp. 251 */ 252 @Parameter(defaultValue = "SNAPSHOT") 253 private String snapshotEnv; 254 255 /** 256 * Template for replacing the SNAPSHOT value. A timestamp format can be provided in brackets. 257 * prefix[yyMMdd]suffix -> prefix151230suffix 258 */ 259 @Parameter 260 private String snapshotTemplate; 261 262 /** 263 * If verbose is true more build messages are logged. 264 */ 265 @Parameter(defaultValue = "false") 266 private boolean verbose; 267 268 /** 269 * Indicates if the execution should be disabled. If <code>true</code>, nothing will occur during execution. 270 * 271 * @since 1.1 272 */ 273 @Parameter(property = "jdeb.skip", defaultValue = "false") 274 private boolean skip; 275 276 @Parameter(property = "jdeb.skipPOMs", defaultValue = "true") 277 private boolean skipPOMs; 278 279 @Parameter(property = "jdeb.skipSubmodules", defaultValue = "false") 280 private boolean skipSubmodules; 281 282 /** 283 * @deprecated 284 */ 285 @Parameter(defaultValue = "true") 286 private boolean submodules; 287 288 289 /** 290 * If signPackage is true then a origin signature will be placed 291 * in the generated package. 292 */ 293 @Parameter(defaultValue = "false") 294 private boolean signPackage; 295 296 /** 297 * If signChanges is true then changes file will be signed. 298 */ 299 @Parameter(defaultValue = "false") 300 private boolean signChanges; 301 302 /** 303 * Defines which utility is used to verify the signed package 304 */ 305 @Parameter(defaultValue = "debsig-verify") 306 private String signMethod; 307 308 /** 309 * Defines the role to sign with 310 */ 311 @Parameter(defaultValue = "origin") 312 private String signRole; 313 314 /** 315 * The keyring to use for signing operations. 316 */ 317 @Parameter 318 private String keyring; 319 320 /** 321 * The key to use for signing operations. 322 */ 323 @Parameter 324 private String key; 325 326 /** 327 * The passphrase to use for signing operations. 328 */ 329 @Parameter 330 private String passphrase; 331 332 /** 333 * The prefix to use when reading signing variables 334 * from settings. 335 */ 336 @Parameter(defaultValue = "jdeb.") 337 private String signCfgPrefix; 338 339 /** 340 * The settings. 341 */ 342 @Parameter(defaultValue = "${settings}") 343 private Settings settings; 344 345 @Parameter(defaultValue = "") 346 private String propertyPrefix; 347 348 /** 349 * Sets the long file mode for the resulting tar file. Valid values are "gnu", "posix", "error" or "truncate" 350 * @see org.apache.commons.compress.archivers.tar.TarArchiveOutputStream#setLongFileMode(int) 351 */ 352 @Parameter(defaultValue = "gnu") 353 private String tarLongFileMode; 354 355 /** 356 * Sets the big number mode for the resulting tar file. Valid values are "gnu", "posix" or "error" 357 * @see org.apache.commons.compress.archivers.tar.TarArchiveOutputStream#setBigNumberMode(int) 358 */ 359 @Parameter(defaultValue = "gnu") 360 private String tarBigNumberMode; 361 362 /* end of parameters */ 363 364 private static final String KEY = "key"; 365 private static final String KEYRING = "keyring"; 366 private static final String PASSPHRASE = "passphrase"; 367 368 private String openReplaceToken = "[["; 369 private String closeReplaceToken = "]]"; 370 private Console console; 371 private Collection<DataProducer> dataProducers = new ArrayList<DataProducer>(); 372 private Collection<DataProducer> conffileProducers = new ArrayList<DataProducer>(); 373 374 public void setOpenReplaceToken( String openReplaceToken ) { 375 this.openReplaceToken = openReplaceToken; 376 } 377 378 public void setCloseReplaceToken( String closeReplaceToken ) { 379 this.closeReplaceToken = closeReplaceToken; 380 } 381 382 protected void setData( Data[] dataSet ) { 383 this.dataSet = dataSet; 384 dataProducers.clear(); 385 conffileProducers.clear(); 386 if (dataSet != null) { 387 Collections.addAll(dataProducers, dataSet); 388 389 for (Data item : dataSet) { 390 if (item.getConffile()) { 391 conffileProducers.add(item); 392 } 393 } 394 } 395 } 396 397 protected VariableResolver initializeVariableResolver( Map<String, String> variables ) { 398 @SuppressWarnings("unchecked") 399 final Map<String, String> projectProperties = Map.class.cast(getProject().getProperties()); 400 @SuppressWarnings("unchecked") 401 final Map<String, String> systemProperties = Map.class.cast(System.getProperties()); 402 403 variables.putAll(projectProperties); 404 variables.putAll(systemProperties); 405 variables.put("name", name != null ? name : getProject().getName()); 406 variables.put("artifactId", getProject().getArtifactId()); 407 variables.put("groupId", getProject().getGroupId()); 408 variables.put("version", getProjectVersion()); 409 variables.put("description", getProject().getDescription()); 410 variables.put("extension", "deb"); 411 variables.put("baseDir", getProject().getBasedir().getAbsolutePath()); 412 variables.put("buildDir", buildDirectory.getAbsolutePath()); 413 variables.put("project.version", getProject().getVersion()); 414 415 if (getProject().getInceptionYear() != null) { 416 variables.put("project.inceptionYear", getProject().getInceptionYear()); 417 } 418 if (getProject().getOrganization() != null) { 419 if (getProject().getOrganization().getName() != null) { 420 variables.put("project.organization.name", getProject().getOrganization().getName()); 421 } 422 if (getProject().getOrganization().getUrl() != null) { 423 variables.put("project.organization.url", getProject().getOrganization().getUrl()); 424 } 425 } 426 427 variables.put("url", getProject().getUrl()); 428 429 return new MapVariableResolver(variables); 430 } 431 432 /** 433 * Doc some cleanup and conversion on the Maven project version. 434 * <ul> 435 * <li>any "-" is replaced by "+"</li> 436 * <li>"SNAPSHOT" is replaced with the current time and date, prepended by "~"</li> 437 * </ul> 438 * 439 * @return the Maven project version 440 */ 441 private String getProjectVersion() { 442 return Utils.convertToDebianVersion(getProject().getVersion(), this.snapshotExpand, this.snapshotEnv, this.snapshotTemplate, session.getStartTime()); 443 } 444 445 /** 446 * @return whether the artifact is a POM or not 447 */ 448 private boolean isPOM() { 449 String type = getProject().getArtifact().getType(); 450 return "pom".equalsIgnoreCase(type); 451 } 452 453 /** 454 * @return whether the artifact is of configured type (i.e. the package to generate is the main artifact) 455 */ 456 private boolean isType() { 457 return type.equals(getProject().getArtifact().getType()); 458 } 459 460 /** 461 * @return whether or not Maven is currently operating in the execution root 462 */ 463 private boolean isSubmodule() { 464 // FIXME there must be a better way 465 return !session.getExecutionRootDirectory().equalsIgnoreCase(baseDir.toString()); 466 } 467 468 /** 469 * @return whether or not the main artifact was created 470 */ 471 private boolean hasMainArtifact() { 472 final MavenProject project = getProject(); 473 final Artifact artifact = project.getArtifact(); 474 return artifact.getFile() != null && artifact.getFile().isFile(); 475 } 476 477 /** 478 * Main entry point 479 * 480 * @throws MojoExecutionException on error 481 */ 482 public void execute() throws MojoExecutionException { 483 484 final MavenProject project = getProject(); 485 486 if (skip) { 487 getLog().info("skipping as configured (skip)"); 488 return; 489 } 490 491 if (skipPOMs && isPOM()) { 492 getLog().info("skipping because artifact is a pom (skipPOMs)"); 493 return; 494 } 495 496 if (skipSubmodules && isSubmodule()) { 497 getLog().info("skipping submodule (skipSubmodules)"); 498 return; 499 } 500 501 502 setData(dataSet); 503 504 console = new MojoConsole(getLog(), verbose); 505 506 initializeSignProperties(); 507 508 final VariableResolver resolver = initializeVariableResolver(new HashMap<String, String>()); 509 510 final File debFile = new File(Utils.replaceVariables(resolver, deb, openReplaceToken, closeReplaceToken)); 511 final File controlDirFile = new File(Utils.replaceVariables(resolver, controlDir, openReplaceToken, closeReplaceToken)); 512 final File installDirFile = new File(Utils.replaceVariables(resolver, installDir, openReplaceToken, closeReplaceToken)); 513 final File changesInFile = new File(Utils.replaceVariables(resolver, changesIn, openReplaceToken, closeReplaceToken)); 514 final File changesOutFile = new File(Utils.replaceVariables(resolver, changesOut, openReplaceToken, closeReplaceToken)); 515 final File changesSaveFile = new File(Utils.replaceVariables(resolver, changesSave, openReplaceToken, closeReplaceToken)); 516 final File keyringFile = keyring == null ? null : new File(Utils.replaceVariables(resolver, keyring, openReplaceToken, closeReplaceToken)); 517 518 // if there are no producers defined we try to use the artifacts 519 if (dataProducers.isEmpty()) { 520 521 if (hasMainArtifact()) { 522 Set<Artifact> artifacts = new HashSet<Artifact>(); 523 524 artifacts.add(project.getArtifact()); 525 526 @SuppressWarnings("unchecked") 527 final Set<Artifact> projectArtifacts = project.getArtifacts(); 528 529 for (Artifact artifact : projectArtifacts) { 530 artifacts.add(artifact); 531 } 532 533 @SuppressWarnings("unchecked") 534 final List<Artifact> attachedArtifacts = project.getAttachedArtifacts(); 535 536 for (Artifact artifact : attachedArtifacts) { 537 artifacts.add(artifact); 538 } 539 540 for (Artifact artifact : artifacts) { 541 final File file = artifact.getFile(); 542 if (file != null) { 543 dataProducers.add(new DataProducer() { 544 public void produce( final DataConsumer receiver ) { 545 try { 546 final File path = new File(installDirFile.getPath(), file.getName()); 547 final String entryName = path.getPath(); 548 549 final boolean symbolicLink = SymlinkUtils.isSymbolicLink(path); 550 final TarArchiveEntry e; 551 if (symbolicLink) { 552 e = new TarArchiveEntry(entryName, TarConstants.LF_SYMLINK); 553 e.setLinkName(SymlinkUtils.readSymbolicLink(path)); 554 } else { 555 e = new TarArchiveEntry(entryName, true); 556 } 557 558 e.setUserId(0); 559 e.setGroupId(0); 560 e.setUserName("root"); 561 e.setGroupName("root"); 562 e.setMode(TarEntry.DEFAULT_FILE_MODE); 563 e.setSize(file.length()); 564 565 receiver.onEachFile(new FileInputStream(file), e); 566 } catch (Exception e) { 567 getLog().error(e); 568 } 569 } 570 }); 571 } else { 572 getLog().error("No file for artifact " + artifact); 573 } 574 } 575 } 576 } 577 578 try { 579 DebMaker debMaker = new DebMaker(console, dataProducers, conffileProducers); 580 debMaker.setDeb(debFile); 581 debMaker.setControl(controlDirFile); 582 debMaker.setPackage(getProject().getArtifactId()); 583 debMaker.setDescription(getProject().getDescription()); 584 debMaker.setHomepage(getProject().getUrl()); 585 debMaker.setChangesIn(changesInFile); 586 debMaker.setChangesOut(changesOutFile); 587 debMaker.setChangesSave(changesSaveFile); 588 debMaker.setCompression(compression); 589 debMaker.setKeyring(keyringFile); 590 debMaker.setKey(key); 591 debMaker.setPassphrase(passphrase); 592 debMaker.setSignPackage(signPackage); 593 debMaker.setSignChanges(signChanges); 594 debMaker.setSignMethod(signMethod); 595 debMaker.setSignRole(signRole); 596 debMaker.setResolver(resolver); 597 debMaker.setOpenReplaceToken(openReplaceToken); 598 debMaker.setCloseReplaceToken(closeReplaceToken); 599 debMaker.setDigest(digest); 600 debMaker.setTarBigNumberMode(tarBigNumberMode); 601 debMaker.setTarLongFileMode(tarLongFileMode); 602 debMaker.validate(); 603 debMaker.makeDeb(); 604 605 // Always attach unless explicitly set to false 606 if ("true".equalsIgnoreCase(attach)) { 607 console.info("Attaching created debian package " + debFile); 608 if (!isType()) { 609 projectHelper.attachArtifact(project, type, classifier, debFile); 610 } else { 611 project.getArtifact().setFile(debFile); 612 } 613 } 614 615 } catch (PackagingException e) { 616 getLog().error("Failed to create debian package " + debFile, e); 617 throw new MojoExecutionException("Failed to create debian package " + debFile, e); 618 } 619 620 if (!isBlank(propertyPrefix)) { 621 project.getProperties().put(propertyPrefix+"version", getProjectVersion() ); 622 project.getProperties().put(propertyPrefix+"deb", debFile.getAbsolutePath()); 623 project.getProperties().put(propertyPrefix+"deb.name", debFile.getName()); 624 project.getProperties().put(propertyPrefix+"changes", changesOutFile.getAbsolutePath()); 625 project.getProperties().put(propertyPrefix+"changes.name", changesOutFile.getName()); 626 project.getProperties().put(propertyPrefix+"changes.txt", changesSaveFile.getAbsolutePath()); 627 project.getProperties().put(propertyPrefix+"changes.txt.name", changesSaveFile.getName()); 628 } 629 630 } 631 632 /** 633 * Initializes unspecified sign properties using available defaults 634 * and global settings. 635 */ 636 private void initializeSignProperties() { 637 if (!signPackage && !signChanges) { 638 return; 639 } 640 641 if (key != null && keyring != null && passphrase != null) { 642 return; 643 } 644 645 Map<String, String> properties = 646 readPropertiesFromActiveProfiles(signCfgPrefix, KEY, KEYRING, PASSPHRASE); 647 648 key = lookupIfEmpty(key, properties, KEY); 649 keyring = lookupIfEmpty(keyring, properties, KEYRING); 650 passphrase = decrypt(lookupIfEmpty(passphrase, properties, PASSPHRASE)); 651 652 if (keyring == null) { 653 try { 654 keyring = Utils.guessKeyRingFile().getAbsolutePath(); 655 console.info("Located keyring at " + keyring); 656 } catch (FileNotFoundException e) { 657 console.warn(e.getMessage()); 658 } 659 } 660 } 661 662 /** 663 * Decrypts given passphrase if needed using maven security dispatcher. 664 * See http://maven.apache.org/guides/mini/guide-encryption.html for details. 665 * 666 * @param maybeEncryptedPassphrase possibly encrypted passphrase 667 * @return decrypted passphrase 668 */ 669 private String decrypt( final String maybeEncryptedPassphrase ) { 670 if (maybeEncryptedPassphrase == null) { 671 return null; 672 } 673 674 try { 675 final String decrypted = secDispatcher.decrypt(maybeEncryptedPassphrase); 676 if (maybeEncryptedPassphrase.equals(decrypted)) { 677 console.info("Passphrase was not encrypted"); 678 } else { 679 console.info("Passphrase was successfully decrypted"); 680 } 681 return decrypted; 682 } catch (SecDispatcherException e) { 683 console.warn("Unable to decrypt passphrase: " + e.getMessage()); 684 } 685 686 return maybeEncryptedPassphrase; 687 } 688 689 /** 690 * 691 * @return the maven project used by this mojo 692 */ 693 private MavenProject getProject() { 694 if (project.getExecutionProject() != null) { 695 return project.getExecutionProject(); 696 } 697 698 return project; 699 } 700 701 702 703 /** 704 * Read properties from the active profiles. 705 * 706 * Goes through all active profiles (in the order the 707 * profiles are defined in settings.xml) and extracts 708 * the desired properties (if present). The prefix is 709 * used when looking up properties in the profile but 710 * not in the returned map. 711 * 712 * @param prefix The prefix to use or null if no prefix should be used 713 * @param properties The properties to read 714 * 715 * @return A map containing the values for the properties that were found 716 */ 717 public Map<String, String> readPropertiesFromActiveProfiles( final String prefix, 718 final String... properties ) { 719 if (settings == null) { 720 console.debug("No maven setting injected"); 721 return Collections.emptyMap(); 722 } 723 724 final List<String> activeProfilesList = settings.getActiveProfiles(); 725 if (activeProfilesList.isEmpty()) { 726 console.debug("No active profiles found"); 727 return Collections.emptyMap(); 728 } 729 730 final Map<String, String> map = new HashMap<String, String>(); 731 final Set<String> activeProfiles = new HashSet<String>(activeProfilesList); 732 733 // Iterate over all active profiles in order 734 for (final Profile profile : settings.getProfiles()) { 735 // Check if the profile is active 736 final String profileId = profile.getId(); 737 if (activeProfiles.contains(profileId)) { 738 console.debug("Trying active profile " + profileId); 739 for (final String property : properties) { 740 final String propKey = prefix != null ? prefix + property : property; 741 final String value = profile.getProperties().getProperty(propKey); 742 if (value != null) { 743 console.debug("Found property " + property + " in profile " + profileId); 744 map.put(property, value); 745 } 746 } 747 } 748 } 749 750 return map; 751 } 752 753}