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>**&#47;.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}