http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/pom.xml
----------------------------------------------------------------------
diff --git a/profile/pom.xml b/profile/pom.xml
new file mode 100644
index 0000000..086355b
--- /dev/null
+++ b/profile/pom.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+    <!--
+
+        Licensed to the Apache Software Foundation (ASF) under one or more
+        contributor license agreements.  See the NOTICE file distributed with
+        this work for additional information regarding copyright ownership.
+        The ASF licenses this file to You under the Apache License, Version 2.0
+        (the "License"); you may not use this file except in compliance with
+        the License.  You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+        Unless required by applicable law or agreed to in writing, software
+        distributed under the License is distributed on an "AS IS" BASIS,
+        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
implied.
+        See the License for the specific language governing permissions and
+        limitations under the License.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf</groupId>
+        <artifactId>karaf</artifactId>
+        <version>4.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.profile</groupId>
+    <artifactId>org.apache.karaf.profile.core</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Profile :: Core</name>
+    <description>Profile Services and API</description>
+
+    <properties>
+        
<appendedResourcesDirectory>${basedir}/../../etc/appended-resources</appendedResourcesDirectory>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.jledit</groupId>
+            <artifactId>core</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*.info</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Export-Package>
+                            org.apache.karaf.profile,
+                            org.apache.karaf.profile.command,
+                            org.apache.karaf.profile.command.completers,
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.karaf.profile.command,
+                            org.apache.karaf.profile.command.completers,
+                            org.apache.karaf.profile.impl,
+                            org.apache.karaf.profile.impl.osgi,
+                            org.apache.karaf.util.tracker,
+                            org.apache.karaf.util.properties,
+                            org.apache.felix.utils.properties,
+                        </Private-Package>
+                        <Provide-Capability>
+                            ${capabilities}
+                        </Provide-Capability>
+                        <Require-Capability>
+                            ${requirements}
+                        </Require-Capability>
+                        <Bundle-Activator>
+                            org.apache.karaf.profile.impl.osgi.Activator
+                        </Bundle-Activator>
+                        <Karaf-Commands>
+                            org.apache.karaf.profile.command.*
+                        </Karaf-Commands>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/LockHandle.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/LockHandle.java 
b/profile/src/main/java/org/apache/karaf/profile/LockHandle.java
new file mode 100644
index 0000000..655c70f
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/LockHandle.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile;
+
+/**
+ * A handle on an acquired read or write lock.
+ */
+public interface LockHandle extends AutoCloseable {
+
+    /**
+     * Release the lock
+     */
+    void close();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/PlaceholderResolver.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/PlaceholderResolver.java 
b/profile/src/main/java/org/apache/karaf/profile/PlaceholderResolver.java
new file mode 100644
index 0000000..7dfeace
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/PlaceholderResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile;
+
+import java.util.Map;
+
+public interface PlaceholderResolver {
+
+    String CATCH_ALL_SCHEME = "*";
+
+    /**
+     * The placeholder scheme.
+     */
+    public String getScheme();
+
+    /**
+     * Resolves the placeholder found inside the value, for the specific key 
of the pid.
+     * @param profile   The current profile
+     * @param pid       The pid that contains the placeholder.
+     * @param key       The key of the configuration value that contains the 
placeholder.
+     * @param value     The value with the placeholder.
+     * @return          The resolved value or EMPTY_STRING.
+     */
+    public String resolve(Map<String, Map<String, String>> profile, String 
pid, String key, String value);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/Profile.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/Profile.java 
b/profile/src/main/java/org/apache/karaf/profile/Profile.java
new file mode 100644
index 0000000..13d0c53
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/Profile.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The immutable view of a profile
+ */
+public interface Profile {
+
+    /**
+     * The attribute key for the list of parents
+     */
+    String PARENTS = "parents";
+
+    /**
+     * The attribute key for the description of the profile
+     */
+    String DESCRIPTION = "description";
+
+    /**
+     * The attribute key for the abstract flag
+     */
+    String ABSTRACT = "abstract";
+
+    /**
+     * The attribute key for the hidden flag
+     */
+    String HIDDEN = "hidden";
+
+    /**
+     * Key indicating a deletion.
+     * This value can appear as the value of a key in a configuration
+     * or as a key itself.  If used as a key, the whole configuration
+     * is flagged has been deleted from its parent when computing the
+     * overlay.
+     */
+    String DELETED = "#deleted#";
+
+    /**
+     * The pid of the configuration holding internal profile attributes
+     */
+    String INTERNAL_PID = "profile";
+
+    /**
+     * The file suffix for a configuration
+     */
+    String PROPERTIES_SUFFIX = ".cfg";
+
+    /**
+     * The attribute prefix for in the agent configuration
+     */
+    String ATTRIBUTE_PREFIX = "attribute.";
+
+    Map<String, String> getAttributes();
+
+    List<String> getParentIds();
+
+    List<String> getLibraries();
+    List<String> getEndorsedLibraries();
+    List<String> getExtensionLibraries();
+    List<String> getBundles();
+    List<String> getFeatures();
+    List<String> getRepositories();
+    List<String> getOverrides();
+    List<String> getOptionals();
+
+    String getId();
+
+    /**
+     * Get the configuration file names that are available on this profile
+     */
+    Set<String> getConfigurationFileNames();
+
+    /**
+     * Get all file configurations
+     */
+    Map<String, byte[]> getFileConfigurations();
+
+    /**
+     * Get the configuration file for the given name
+     */
+    byte[] getFileConfiguration(String fileName);
+
+    /**
+     * Get all configuration properties
+     */
+    Map<String, Map<String, String>> getConfigurations();
+
+    /**
+     * Get the configuration properties for the given PID
+     * @return an empty map if the there is no configuration for the given pid
+     */
+    Map<String, String> getConfiguration(String pid);
+
+    /**
+     * Indicate if this profile is an overlay or not.
+     */
+    boolean isOverlay();
+
+    /**
+     * Returns true if this profile is Abstract. 
+     * Abstract profiles should not be provisioned by default, they are 
intended to be inherited
+     */
+    boolean isAbstract();
+
+    /**
+     * Returns true if this profile is hidden.  
+     * Hidden profiles are not listed by default.
+     */
+    boolean isHidden();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/ProfileBuilder.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/ProfileBuilder.java 
b/profile/src/main/java/org/apache/karaf/profile/ProfileBuilder.java
new file mode 100644
index 0000000..4638d5e
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/ProfileBuilder.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.karaf.profile.impl.ProfileBuilderImpl;
+
+
+/**
+ * A profile builder.
+ */
+public interface ProfileBuilder {
+
+    ProfileBuilder addAttribute(String key, String value);
+
+    ProfileBuilder setAttributes(Map<String, String> attributes);
+
+    ProfileBuilder from(Profile profile);
+    
+    ProfileBuilder identity(String profileId);
+
+    List<String> getParents();
+    
+    ProfileBuilder addParent(String parentId);
+
+    ProfileBuilder addParents(List<String> parentIds);
+
+    ProfileBuilder setParents(List<String> parentIds);
+    
+    ProfileBuilder removeParent(String parentId);
+
+    Set<String> getConfigurationKeys();
+    
+    Map<String, String> getConfiguration(String pid);
+    
+    ProfileBuilder addConfiguration(String pid, Map<String, String> config);
+
+    ProfileBuilder addConfiguration(String pid, String key, String value);
+
+    ProfileBuilder setConfigurations(Map<String, Map<String, String>> configs);
+
+    ProfileBuilder deleteConfiguration(String pid);
+
+    Set<String> getFileConfigurationKeys();
+    
+    byte[] getFileConfiguration(String key);
+    
+    ProfileBuilder addFileConfiguration(String fileName, byte[] data);
+    
+    ProfileBuilder setFileConfigurations(Map<String, byte[]> configs);
+
+    ProfileBuilder deleteFileConfiguration(String fileName);
+    
+    ProfileBuilder setBundles(List<String> values);
+
+    ProfileBuilder addBundle(String value);
+
+    ProfileBuilder setFeatures(List<String> values);
+
+    ProfileBuilder addFeature(String value);
+
+    ProfileBuilder setRepositories(List<String> values);
+
+    ProfileBuilder addRepository(String value);
+
+    ProfileBuilder setOverrides(List<String> values);
+    
+    ProfileBuilder setOptionals(List<String> values);
+    
+    ProfileBuilder setOverlay(boolean overlay);
+    
+    Profile getProfile();
+
+    final class Factory {
+
+        public static ProfileBuilder create() {
+            return new ProfileBuilderImpl();
+        }
+
+        public static ProfileBuilder create(String profileId) {
+            return new ProfileBuilderImpl().identity(profileId);
+        }
+
+        public static ProfileBuilder createFrom(Profile profile) {
+            return new ProfileBuilderImpl().from(profile);
+        }
+
+        // Hide ctor
+        private Factory() {
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/ProfileService.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/ProfileService.java 
b/profile/src/main/java/org/apache/karaf/profile/ProfileService.java
new file mode 100644
index 0000000..237c1f6
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/ProfileService.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile;
+
+import java.util.Collection;
+
+/**
+ * The profile service
+ */
+public interface ProfileService {
+
+    //
+    // Lock management
+    //
+
+    /**
+     * Acquire a write lock for the profile.
+     */
+    LockHandle acquireWriteLock();
+
+    /**
+     * Acquire a read lock for the profile.
+     * A read lock cannot be upgraded to a write lock.
+     */
+    LockHandle acquireReadLock();
+
+    //
+    // PlaceholderResolver management
+    //
+
+    /**
+     * Register the given resolver.
+     * @param resolver the resolver to register
+     */
+    void registerResolver(PlaceholderResolver resolver);
+
+    /**
+     * Unregister the given resolver.
+     * @param resolver the resolver to unregister
+     */
+    void unregisterResolver(PlaceholderResolver resolver);
+
+    //
+    // Profile management
+    //
+    
+    /**
+     * Create the given profile in the data store.
+     */
+    void createProfile(Profile profile);
+    
+    /**
+     * Create the given profile in the data store.
+     */
+    void updateProfile(Profile profile);
+
+    /**
+     * True if the given profile exists in the given version.
+     */
+    boolean hasProfile(String profileId);
+
+    /**
+     * Get the profile for the given version and id.
+     * @return The profile or null
+     */
+    Profile getProfile(String profileId);
+
+    /**
+     * Get the profile for the given version and id.
+     * @throws IllegalStateException if the required profile does not exist
+     */
+    Profile getRequiredProfile(String profileId);
+
+    /** 
+     * Get the list of profiles associated with the given version.
+     */
+    Collection<String> getProfiles();
+
+    /**
+     * Delete the given profile from the data store.
+     */
+    void deleteProfile(String profileId);
+
+    /**
+     * Compute the overlay profile.
+     *
+     * The overlay profile is computed by getting all the parent profiles
+     * and overriding the settings by children profiles.
+     */
+    Profile getOverlayProfile(Profile profile);
+
+    /**
+     * Compute the overlay profile.
+     *
+     * The overlay profile is computed by getting all the parent profiles
+     * and overriding the settings by children profiles.
+     */
+    Profile getOverlayProfile(Profile profile, String environment);
+
+    /**
+     * Compute the effective profile.
+     *
+     * The effective profile is computed by performing all substitutions
+     * in the given profile configurations.
+     */
+    Profile getEffectiveProfile(Profile profile);
+
+    /**
+     * Compute the effective profile.
+     *
+     * The effective profile is computed by performing all substitutions
+     * in the given profile configurations.
+     *
+     * @param defaultsToEmptyString if no substitution is valid, defaults to 
an empty string
+     */
+    Profile getEffectiveProfile(Profile profile, boolean 
defaultsToEmptyString);
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileChangeParents.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileChangeParents.java
 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileChangeParents.java
new file mode 100644
index 0000000..ef3d10f
--- /dev/null
+++ 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileChangeParents.java
@@ -0,0 +1,56 @@
+/**
+ *  Copyright 2005-2014 Red Hat, Inc.
+ *
+ *  Red Hat licenses this file to you under the Apache License, version
+ *  2.0 (the "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ *  implied.  See the License for the specific language governing
+ *  permissions and limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import java.util.List;
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "change-parents", scope = "profile", description = "Replace 
the profile's parents with the specified list of parents")
+@Service
+public class ProfileChangeParents implements Action {
+
+    @Argument(index = 0, required = true, name = "profile", description = 
"Name of the profile.")
+    @Completion(ProfileCompleter.class)
+    private String profileId;
+
+    @Argument(index = 1, name = "parents", description = "The list of new 
parent profiles.", required = true, multiValued = true)
+    @Completion(ProfileCompleter.class)
+    private List<String> parentIds;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        Profile profile = profileService.getRequiredProfile(profileId);
+        Profile newProfile = ProfileBuilder.Factory.createFrom(profile)
+                .addParents(parentIds)
+                .getProfile();
+        profileService.updateProfile(newProfile);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileCopy.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileCopy.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileCopy.java
new file mode 100644
index 0000000..6855acd
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileCopy.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "copy", scope = "profile", description = "Copies the specified 
source profile")
+@Service
+public class ProfileCopy implements Action {
+
+    @Option(name = "-f", aliases = "--force", description = "Flag to allow 
overwriting the target profile (if exists).")
+    private boolean force;
+
+    @Argument(index = 0, required = true, name = "source profile", description 
= "Name of the source profile.")
+    @Completion(ProfileCompleter.class)
+    private String source;
+
+    @Argument(index = 1, required = true, name = "target profile", description 
= "Name of the target profile.")
+    private String target;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        Profile profile = 
ProfileBuilder.Factory.createFrom(profileService.getProfile(source))
+                .identity(target)
+                .getProfile();
+        profileService.createProfile(profile);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileCreate.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileCreate.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileCreate.java
new file mode 100644
index 0000000..2b7abd5
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileCreate.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import java.util.List;
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "create", scope = "profile", description = "Create a new 
profile with the specified name and parents", detailedDescription = 
"classpath:profileCreate.txt")
+@Service
+public class ProfileCreate implements Action {
+
+    @Option(name = "--version", description = "The profile version. Defaults 
to the current default version.")
+    private String versionId;
+    @Option(name = "--parents", multiValued = true, required = false, 
description = "Optionally specifies one or multiple parent profiles. To specify 
multiple parent profiles, specify this flag multiple times on the command line. 
For example, --parents foo --parents bar.")
+    private List<String> parents;
+    @Argument(index = 0)
+    private String profileId;
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        Profile profile = ProfileBuilder.Factory.create(profileId)
+                .setParents(parents)
+                .getProfile();
+               profileService.createProfile(profile);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileDelete.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileDelete.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDelete.java
new file mode 100644
index 0000000..b8afe1e
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDelete.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "delete", scope = "profile", description = "Delete the 
specified profile")
+@Service
+public class ProfileDelete implements Action {
+
+    @Argument(index = 0, required = true, name = "profile", description = 
"Name of the profile to delete.")
+    @Completion(ProfileCompleter.class)
+    private String profileId;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        profileService.deleteProfile(profileId);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
new file mode 100644
index 0000000..5eb65d0
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileDisplay.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "display", scope = "profile", description = "Displays 
information about the specified profile")
+@Service
+public class ProfileDisplay implements Action {
+
+    @Option(name = "--overlay", aliases = "-o", description = "Shows the 
overlay profile settings, taking into account the settings inherited from 
parent profiles.")
+    private Boolean overlay = false;
+
+    @Option(name = "--effective", aliases = "-e", description = "Shows the 
effective profile settings, taking into account properties substitution.")
+    private Boolean effective = false;
+
+    @Option(name = "--display-resources", aliases = "-r", description = 
"Displays the content of additional profile resources.")
+    private Boolean displayResources = false;
+
+    @Argument(index = 0, required = true, name = "profile", description = "The 
name of the profile.")
+    @Completion(ProfileCompleter.class)
+    private String profileId;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        displayProfile(profileService.getRequiredProfile(profileId));
+        return null;
+    }
+
+    private static void printConfigList(String header, PrintStream out, 
List<String> list) {
+        out.println(header);
+        for (String str : list) {
+            out.printf("\t%s\n", str);
+        }
+        out.println();
+    }
+
+    private void displayProfile(Profile profile) {
+        PrintStream output = System.out;
+
+        output.println("Profile id: " + profile.getId());
+
+        output.println("Attributes: ");
+        Map<String, String> props = profile.getAttributes();
+        for (String key : props.keySet()) {
+            output.println("\t" + key + ": " + props.get(key));
+        }
+
+        if (overlay) {
+            profile = profileService.getOverlayProfile(profile);
+        }
+        if (effective) {
+            profile = profileService.getEffectiveProfile(profile);
+        }
+
+        Map<String, Map<String, String>> configuration = new 
HashMap<>(profile.getConfigurations());
+        Map<String, byte[]> resources = profile.getFileConfigurations();
+        Map<String,String> agentConfiguration = 
profile.getConfiguration(Profile.INTERNAL_PID);
+        List<String> agentProperties = new ArrayList<String>();
+        List<String> systemProperties = new ArrayList<String>();
+        List<String> configProperties = new ArrayList<String>();
+        List<String> otherResources = new ArrayList<String>();
+        for (Map.Entry<String, String> entry : agentConfiguration.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            if (value.contains(",")) {
+                value = "\t" + value.replace(",", ",\n\t\t");
+            }
+
+            if (key.startsWith("system.")) {
+                systemProperties.add("  " + key.substring("system.".length()) 
+ " = " + value);
+            }
+            else if (key.startsWith("config.")) {
+                configProperties.add("  " + key.substring("config.".length()) 
+ " = " + value);
+            }
+            else if (!key.startsWith("feature.") && 
!key.startsWith("repository") &&
+                        !key.startsWith("bundle.") && !key.startsWith("fab.") 
&&
+                        !key.startsWith("override.") && 
!key.startsWith("attribute.")) {
+                agentProperties.add("  " + key + " = " + value);
+            }
+        }
+
+        if (configuration.containsKey(Profile.INTERNAL_PID)) {
+            output.println("\nContainer settings");
+            output.println("----------------------------");
+
+            if (profile.getLibraries().size() > 0) {
+                printConfigList("Libraries : ", output, 
profile.getLibraries());
+            }
+            if (profile.getRepositories().size() > 0) {
+                printConfigList("Repositories : ", output, 
profile.getRepositories());
+            }
+            if (profile.getFeatures().size() > 0) {
+                printConfigList("Features : ", output, profile.getFeatures());
+            }
+            if (profile.getBundles().size() > 0) {
+                printConfigList("Bundles : ", output, profile.getBundles());
+            }
+            if (profile.getOverrides().size() > 0) {
+                printConfigList("Overrides : ", output, 
profile.getOverrides());
+            }
+
+            if (agentProperties.size() > 0) {
+                printConfigList("Agent Properties : ", output, 
agentProperties);
+            }
+
+            if (systemProperties.size() > 0) {
+                printConfigList("System Properties : ", output, 
systemProperties);
+            }
+
+            if (configProperties.size() > 0) {
+                printConfigList("Config Properties : ", output, 
configProperties);
+            }
+
+            configuration.remove(Profile.INTERNAL_PID);
+        }
+
+        output.println("\nConfiguration details");
+        output.println("----------------------------");
+        for (Map.Entry<String, Map<String, String>> cfg : 
configuration.entrySet()) {
+            output.println("PID: " + cfg.getKey());
+
+            for (Map.Entry<String, String> values : cfg.getValue().entrySet()) 
{
+                output.println("  " + values.getKey() + " " + 
values.getValue());
+            }
+            output.println("\n");
+        }
+
+        output.println("\nOther resources");
+        output.println("----------------------------");
+        for (Map.Entry<String,byte[]> resource : resources.entrySet()) {
+            String name = resource.getKey();
+            if (!name.endsWith(".properties")) {
+                output.println("Resource: " + resource.getKey());
+                if (displayResources) {
+                    output.println(new String(resource.getValue()));
+                    output.println("\n");
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
new file mode 100644
index 0000000..d76eb57
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileEdit.java
@@ -0,0 +1,611 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import jline.TerminalSupport;
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Terminal;
+import org.jledit.ConsoleEditor;
+import org.jledit.ContentManager;
+import org.jledit.EditorFactory;
+import org.jledit.simple.SimpleConsoleEditor;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+@Command(name = "edit", scope = "profile", description = "Edits the specified 
profile", detailedDescription = "classpath:profileEdit.txt")
+@Service
+public class ProfileEdit implements Action {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(ProfileEdit.class);
+
+    static final String FEATURE_PREFIX = "feature.";
+    static final String REPOSITORY_PREFIX = "repository.";
+    static final String BUNDLE_PREFIX = "bundle.";
+    static final String OVERRIDE_PREFIX = "override.";
+    static final String CONFIG_PREFIX = "config.";
+    static final String SYSTEM_PREFIX = "system.";
+    static final String LIB_PREFIX = "lib.";
+    static final String ENDORSED_PREFIX = "endorsed.";
+    static final String EXT_PREFIX = "ext.";
+    static final String DELIMITER = ",";
+    static final String PID_KEY_SEPARATOR = "/";
+
+    static final String FILE_INSTALL_FILENAME_PROPERTY = 
"felix.fileinstall.filename";
+
+
+    @Option(name = "-r", aliases = {"--repositories"}, description = "Edit the 
features repositories. To specify multiple repositories, specify this flag 
multiple times.", required = false, multiValued = true)
+    private String[] repositories;
+
+    @Option(name = "-f", aliases = {"--features"}, description = "Edit 
features. To specify multiple features, specify this flag multiple times. For 
example, --features foo --features bar.", required = false, multiValued = true)
+    private String[] features;
+
+    @Option(name = "-l", aliases = {"--libs"}, description = "Edit libraries. 
To specify multiple libraries, specify this flag multiple times.", required = 
false, multiValued = true)
+    private String[] libs;
+
+    @Option(name = "-n", aliases = {"--endorsed"}, description = "Edit 
endorsed libraries. To specify multiple libraries, specify this flag multiple 
times.", required = false, multiValued = true)
+    private String[] endorsed;
+
+    @Option(name = "-x", aliases = {"--extension"}, description = "Edit 
extension libraries. To specify multiple libraries, specify this flag multiple 
times.", required = false, multiValued = true)
+    private String[] extension;
+
+    @Option(name = "-b", aliases = {"--bundles"}, description = "Edit bundles. 
To specify multiple bundles, specify this flag multiple times.", required = 
false, multiValued = true)
+    private String[] bundles;
+
+    @Option(name = "-o", aliases = {"--overrides"}, description = "Edit 
overrides. To specify multiple libraries, specify this flag multiple times.", 
required = false, multiValued = true)
+    private String[] overrides;
+
+    @Option(name = "-p", aliases = {"--pid"}, description = "Edit an OSGi 
configuration property, specified in the format <PID>/<Property>. To specify 
multiple properties, specify this flag multiple times.", required = false, 
multiValued = true)
+    private String[] pidProperties;
+
+    @Option(name = "-s", aliases = {"--system"}, description = "Edit the Java 
system properties that affect installed bundles (analogous to editing 
etc/system.properties in a root container).", required = false, multiValued = 
true)
+    private String[] systemProperties;
+
+    @Option(name = "-c", aliases = {"--config"}, description = "Edit the Java 
system properties that affect the karaf container (analogous to editing 
etc/config.properties in a root container).", required = false, multiValued = 
true)
+    private String[] configProperties;
+
+    @Option(name = "-i", aliases = {"--import-pid"}, description = "Imports 
the pids that are edited, from local OSGi config admin", required = false, 
multiValued = false)
+    private boolean importPid = false;
+
+    @Option(name = "--resource", description = "Selects a resource under the 
profile to edit. This option should only be used alone.", required = false, 
multiValued = false)
+    private String resource;
+
+    @Option(name = "--set", description = "Set or create values (selected by 
default).")
+    private boolean set = true;
+
+    @Option(name = "--delete", description = "Delete values. This option can 
be used to delete a feature, a bundle or a pid from the profile.")
+    private boolean delete = false;
+
+    @Option(name = "--append", description = "Append value to a delimited 
list. It is only usable with the system, config & pid options")
+    private boolean append = false;
+
+    @Option(name = "--remove", description = "Removes value from a delimited 
list. It is only usable with the system, config & pid options")
+    private boolean remove = false;
+
+    @Option(name = "--delimiter", description = "Specifies the delimiter to 
use for appends and removals.")
+    private String delimiter = ",";
+
+    @Argument(index = 0, name = "profile", description = "The target profile 
to edit", required = true, multiValued = false)
+    private String profileName;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Reference
+    private ConfigurationAdmin configurationAdmin;
+
+    @Reference
+    private EditorFactory editorFactory;
+
+    @Reference
+    Terminal terminal;
+
+    public void init() {
+        // TODO: Karaf 2.4 has a bug to lookup this class, so we bind it 
manually - Karaf 2.4.1 should have this fixed
+        this.editorFactory.bind("simple", SimpleConsoleEditor.class);
+    }
+
+    @Override
+    public Object execute() throws Exception {
+        if (delete) {
+            set = false;
+        }
+
+        Profile profile = profileService.getRequiredProfile(profileName);
+        editProfile(profile);
+        return null;
+    }
+
+    private void editProfile(Profile profile) throws Exception {
+        boolean editInLine = false;
+
+        ProfileBuilder builder = ProfileBuilder.Factory.createFrom(profile);
+        
+        if (delete || remove) {
+            editInLine = true;
+        }
+
+        if (features != null && features.length > 0) {
+            editInLine = true;
+            handleFeatures(builder, features, profile);
+        }
+        if (repositories != null && repositories.length > 0) {
+            editInLine = true;
+            handleFeatureRepositories(builder, repositories, profile);
+        }
+        if (libs != null && libs.length > 0) {
+            editInLine = true;
+            handleLibraries(builder, libs, profile, "lib", LIB_PREFIX);
+        }
+        if (endorsed != null && endorsed.length > 0) {
+            editInLine = true;
+            handleLibraries(builder, endorsed, profile, "endorsed lib", 
ENDORSED_PREFIX);
+        }
+        if (extension != null && extension.length > 0) {
+            editInLine = true;
+            handleLibraries(builder, extension, profile, "extension lib", 
EXT_PREFIX);
+        }
+        if (bundles != null && bundles.length > 0) {
+            editInLine = true;
+            handleBundles(builder, bundles, profile);
+        }
+        if (overrides != null && overrides.length > 0) {
+            editInLine = true;
+            handleOverrides(builder, overrides, profile);
+        }
+
+        if (pidProperties != null && pidProperties.length > 0) {
+            editInLine = handlePid(builder, pidProperties, profile);
+        }
+
+        if (systemProperties != null && systemProperties.length > 0) {
+            editInLine = true;
+            handleSystemProperties(builder, systemProperties, profile);
+        }
+
+        if (configProperties != null && configProperties.length > 0) {
+            editInLine = true;
+            handleConfigProperties(builder, configProperties, profile);
+        }
+
+        if (!editInLine) {
+            if (resource == null) {
+                resource = Profile.INTERNAL_PID + Profile.PROPERTIES_SUFFIX;
+            }
+            //If a single pid has been selected, but not a key value has been 
specified or import has been selected,
+            //then open the resource in the editor.
+            if (pidProperties != null && pidProperties.length == 1) {
+                resource = pidProperties[0] + Profile.PROPERTIES_SUFFIX;
+            }
+            openInEditor(profile, resource);
+        }
+        
+        profileService.updateProfile(builder.getProfile());
+    }
+
+    /**
+     * Adds or remove the specified features to the specified profile.
+     */
+    private void handleFeatures(ProfileBuilder builder, String[] features, 
Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String feature : features) {
+            if (delete) {
+                System.out.println("Deleting feature:" + feature + " from 
profile:" + profile.getId());
+            } else {
+                System.out.println("Adding feature:" + feature + " to 
profile:" + profile.getId());
+            }
+            updateConfig(conf, FEATURE_PREFIX + feature.replace('/', '_'), 
feature, set, delete);
+            builder.addConfiguration(Profile.INTERNAL_PID, conf);
+        }
+    }
+
+    /**
+     * Adds or remove the specified feature repositories to the specified 
profile.
+     */
+    private void handleFeatureRepositories(ProfileBuilder builder, String[] 
repositories, Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String repositoryURI : repositories) {
+            if (set) {
+                System.out.println("Adding feature repository:" + 
repositoryURI + " to profile:" + profile.getId());
+            } else if (delete) {
+                System.out.println("Deleting feature repository:" + 
repositoryURI + " from profile:" + profile.getId());
+            }
+            updateConfig(conf, REPOSITORY_PREFIX + repositoryURI.replace('/', 
'_'), repositoryURI, set, delete);
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    /**
+     * Adds or remove the specified libraries to the specified profile.
+     * @param libs      The array of libs.
+     * @param profile   The target profile.
+     * @param libType   The type of lib. Used just for the command output.
+     * @param libPrefix The prefix of the lib.
+     */
+    private void handleLibraries(ProfileBuilder builder, String[] libs, 
Profile profile, String libType, String libPrefix) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String lib : libs) {
+            if (set) {
+                System.out.println("Adding "+libType+":" + lib + " to 
profile:" + profile.getId());
+            } else if (delete) {
+                System.out.println("Deleting "+libType+":" + lib + " from 
profile:" + profile.getId());
+            }
+            updateConfig(conf, libPrefix + lib.replace('/', '_'), lib, set, 
delete);
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    /**
+     * Adds or remove the specified bundles to the specified profile.
+     * @param bundles   The array of bundles.
+     * @param profile   The target profile.
+     */
+    private void handleBundles(ProfileBuilder builder, String[] bundles, 
Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String bundle : bundles) {
+            if (set) {
+                System.out.println("Adding bundle:" + bundle + " to profile:" 
+ profile.getId());
+            } else if (delete) {
+                System.out.println("Deleting bundle:" + bundle + " from 
profile:" + profile.getId());
+            }
+            updateConfig(conf, BUNDLE_PREFIX + bundle.replace('/', '_'), 
bundle, set, delete);
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    /**
+     * Adds or remove the specified overrides to the specified profile.
+     * @param overrides     The array of overrides.
+     * @param profile       The target profile.
+     */
+    private void handleOverrides(ProfileBuilder builder, String[] overrides, 
Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String override : overrides) {
+            if (set) {
+                System.out.println("Adding override:" + override + " to 
profile:" + profile.getId());
+            } else if (delete) {
+                System.out.println("Deleting override:" + override + " from 
profile:" + profile.getId());
+            }
+            updateConfig(conf, OVERRIDE_PREFIX + override.replace('/', '_'), 
override, set, delete);
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    /**
+     * Adds or remove the specified system properties to the specified profile.
+     * @param pidProperties         The array of system properties.
+     * @param profile               The target profile.
+     * @return                      True if the edit can take place in line.
+     */
+    private boolean handlePid(ProfileBuilder builder, String[] pidProperties, 
Profile profile) {
+        boolean editInline = true;
+        for (String pidProperty : pidProperties) {
+            String currentPid;
+
+            String keyValuePair = "";
+            if (pidProperty.contains(PID_KEY_SEPARATOR)) {
+                currentPid = pidProperty.substring(0, 
pidProperty.indexOf(PID_KEY_SEPARATOR));
+                keyValuePair = 
pidProperty.substring(pidProperty.indexOf(PID_KEY_SEPARATOR) + 1);
+            } else {
+                currentPid = pidProperty;
+            }
+            Map<String, String> conf = getConfigurationFromBuilder(builder, 
currentPid);
+            
+            // We only support import when a single pid is specified
+            if (pidProperties.length == 1 && importPid) {
+                System.out.println("Importing pid:" + currentPid + " to 
profile:" + profile.getId());
+                importPidFromLocalConfigAdmin(currentPid, conf);
+                builder.addConfiguration(currentPid, conf);
+                return true;
+            }
+
+
+            Map<String, String> configMap = extractConfigs(keyValuePair);
+            if (configMap.isEmpty() && set) {
+                editInline = false;
+            } else if (configMap.isEmpty() && delete) {
+                editInline = true;
+                System.out.println("Deleting pid:" + currentPid + " from 
profile:" + profile.getId());
+                builder.deleteConfiguration(currentPid);
+            } else {
+                for (Map.Entry<String, String> configEntries : 
configMap.entrySet()) {
+                    String key = configEntries.getKey();
+                    String value = configEntries.getValue();
+                    if (value == null && delete) {
+                        System.out.println("Deleting key:" + key + " from 
pid:" + currentPid + " and profile:" + profile.getId());
+                        conf.remove(key);
+                    } else {
+                        if (append) {
+                            System.out.println("Appending value:" + value + " 
key:" + key + " to pid:" + currentPid + " and profile:" + profile.getId());
+                        } else if (remove) {
+                            System.out.println("Removing value:" + value + " 
key:" + key + " from pid:" + currentPid + " and profile:" + profile.getId());
+                        } else if(set) {
+                            System.out.println("Setting value:" + value + " 
key:" + key + " on pid:" + currentPid + " and profile:" + profile.getId());
+                        }
+                        updatedDelimitedList(conf, key, value, delimiter, set, 
delete, append, remove);
+                    }
+                }
+                editInline = true;
+                builder.addConfiguration(currentPid, conf);
+            }
+        }
+        return editInline;
+    }
+
+
+    /**
+     * Adds or remove the specified system properties to the specified profile.
+     * @param systemProperties      The array of system properties.
+     * @param profile               The target profile.
+     */
+    private void handleSystemProperties(ProfileBuilder builder, String[] 
systemProperties, Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String systemProperty : systemProperties) {
+            Map<String, String> configMap = extractConfigs(systemProperty);
+            for (Map.Entry<String, String> configEntries : 
configMap.entrySet()) {
+                String key = configEntries.getKey();
+                String value = configEntries.getValue();
+                if (append) {
+                    System.out.println("Appending value:" + value + " key:" + 
key + " from system properties and profile:" + profile.getId());
+                } else if (delete) {
+                    System.out.println("Deleting key:" + key + " from system 
properties and profile:" + profile.getId());
+                } else if (set) {
+                    System.out.println("Setting value:" + value + " key:" + 
key + " from system properties and profile:" + profile.getId());
+                } else {
+                    System.out.println("Removing value:" + value + " key:" + 
key + " from system properties and profile:" + profile.getId());
+                }
+                updatedDelimitedList(conf, SYSTEM_PREFIX + key, value, 
delimiter, set, delete, append, remove);
+            }
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    /**
+     * Adds or remove the specified config properties to the specified profile.
+     * @param configProperties      The array of config properties.
+     * @param profile               The target profile.
+     */
+    private void handleConfigProperties(ProfileBuilder builder, String[] 
configProperties, Profile profile) {
+        Map<String, String> conf = getConfigurationFromBuilder(builder, 
Profile.INTERNAL_PID);
+        for (String configProperty : configProperties) {
+            Map<String, String> configMap = extractConfigs(configProperty);
+            for (Map.Entry<String, String> configEntries : 
configMap.entrySet()) {
+                String key = configEntries.getKey();
+                String value = configEntries.getValue();
+                if (append) {
+                    System.out.println("Appending value:" + value + " key:" + 
key + " from config properties and profile:" + profile.getId());
+                } else if (delete) {
+                    System.out.println("Deleting key:" + key + " from config 
properties and profile:" + profile.getId());
+                } else if (set) {
+                    System.out.println("Setting value:" + value + " key:" + 
key + " from config properties and profile:" + profile.getId());
+                }
+                updatedDelimitedList(conf, CONFIG_PREFIX + key, value, 
delimiter, set, delete, append, remove);
+            }
+        }
+        builder.addConfiguration(Profile.INTERNAL_PID, conf);
+    }
+
+    private void openInEditor(Profile profile, String resource) throws 
Exception {
+        String id = profile.getId();
+        String location = id + " " + resource;
+        //Call the editor
+        ConsoleEditor editor = editorFactory.create("simple", getTerminal());
+        editor.setTitle("Profile");
+        editor.setOpenEnabled(false);
+        editor.setContentManager(new DatastoreContentManager(profileService));
+        editor.open(location, id);
+        editor.start();
+    }
+
+    public void updatedDelimitedList(Map<String, String> map, String key, 
String value, String delimiter, boolean set, boolean delete, boolean append, 
boolean remove) {
+        if (append || remove) {
+            String oldValue = map.containsKey(key) ? map.get(key) : "";
+            List<String> parts = new 
LinkedList<>(Arrays.asList(oldValue.split(delimiter)));
+            //We need to remove any possible blanks.
+            parts.remove("");
+            if (append) {
+                parts.add(value);
+            }
+            if (remove) {
+                parts.remove(value);
+            }
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < parts.size(); i++) {
+                if (i != 0) {
+                    sb.append(delimiter);
+                }
+                sb.append(parts.get(i));
+            }
+            map.put(key, sb.toString());
+        } else if (set) {
+            map.put(key, value);
+        } else if (delete) {
+            map.remove(key);
+        }
+    }
+
+    private void updateConfig(Map<String, String> map, String key, String 
value, boolean set, boolean delete) {
+        if (set) {
+            map.put(key, value);
+        } else if (delete) {
+            map.remove(key);
+        }
+    }
+
+    /**
+     * Imports the pid to the target Map.
+     */
+    private void importPidFromLocalConfigAdmin(String pid, Map<String, String> 
target) {
+        try {
+            Configuration[] configuration = 
configurationAdmin.listConfigurations("(service.pid=" + pid + ")");
+            if (configuration != null && configuration.length > 0) {
+                Dictionary dictionary = configuration[0].getProperties();
+                Enumeration keyEnumeration = dictionary.keys();
+                while (keyEnumeration.hasMoreElements()) {
+                    String key = String.valueOf(keyEnumeration.nextElement());
+                    //file.install.filename needs to be skipped as it specific 
to the current container.
+                    if (!key.equals(FILE_INSTALL_FILENAME_PROPERTY)) {
+                        String value = String.valueOf(dictionary.get(key));
+                        target.put(key, value);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            LOGGER.warn("Error while importing configuration {} to profile.", 
pid);
+        }
+    }
+
+    /**
+     * Extracts Key value pairs from a delimited string of key value pairs.
+     * Note: The value may contain commas.
+     */
+    private Map<String, String> extractConfigs(String configs) {
+        Map<String, String> configMap = new HashMap<>();
+        //If contains key values.
+        String key;
+        String value;
+        if (configs.contains("=")) {
+            key = configs.substring(0, configs.indexOf("="));
+            value = configs.substring(configs.indexOf("=") + 1);
+
+        }  else {
+            key = configs;
+            value = null;
+        }
+        if (!key.isEmpty()) {
+            configMap.put(key, value);
+        }
+        return configMap;
+    }
+
+    /**
+     * Gets the {@link jline.Terminal} from the current session.
+     */
+    private jline.Terminal getTerminal() throws Exception {
+        try {
+            return (jline.Terminal) 
terminal.getClass().getMethod("getTerminal").invoke(terminal);
+        } catch (Throwable t) {
+            return new TerminalSupport(true) {
+                @Override
+                public int getWidth() {
+                    return terminal.getWidth();
+                }
+
+                @Override
+                public int getHeight() {
+                    return terminal.getHeight();
+                }
+            };
+        }
+    }
+
+    private Map<String, String> getConfigurationFromBuilder(ProfileBuilder 
builder, String pid) {
+        Map<String, String> config = builder.getConfiguration(pid);
+        return config != null ? new HashMap<>(config) : new HashMap<String, 
String>();
+    }
+
+    static class DatastoreContentManager implements ContentManager {
+
+        private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+        private final ProfileService profileService;
+
+        public DatastoreContentManager(ProfileService profileService) {
+            this.profileService = profileService;
+        }
+
+        /**
+         * Loads content from the specified location.
+         */
+        @Override
+        public String load(String location) throws IOException {
+            try {
+                String[] parts = location.trim().split(" ");
+                if (parts.length < 3) {
+                    throw new IllegalArgumentException("Invalid location:" + 
location);
+                }
+                String profileId = parts[0];
+                String resource = parts[1];
+                Profile profile = profileService.getRequiredProfile(profileId);
+                String data = new 
String(profile.getFileConfiguration(resource));
+                return data;
+            } catch (Exception e) {
+                throw new IOException("Failed to read data from zookeeper.", 
e);
+            }
+        }
+
+        /**
+         * Saves content to the specified location.
+         */
+        @Override
+        public boolean save(String content, String location) {
+            try {
+                String[] parts = location.trim().split(" ");
+                if (parts.length < 3) {
+                    throw new IllegalArgumentException("Invalid location:" + 
location);
+                }
+                String profileId = parts[0];
+                String resource = parts[1];
+                Profile profile = profileService.getRequiredProfile(profileId);
+                ProfileBuilder builder = 
ProfileBuilder.Factory.createFrom(profile);
+                builder.addFileConfiguration(resource, content.getBytes());
+                profileService.updateProfile(builder.getProfile());
+            } catch (Exception e) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Saves the {@link String} content to the specified location using 
the specified {@link java.nio.charset.Charset}.
+         */
+        @Override
+        public boolean save(String content, Charset charset, String location) {
+            return save(content, location);
+        }
+
+        /**
+         * Detect the Charset of the content in the specified location.
+         */
+        @Override
+        public Charset detectCharset(String location) {
+            return UTF_8;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileList.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileList.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileList.java
new file mode 100644
index 0000000..2a617a9
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileList.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import static org.apache.karaf.profile.impl.Utils.join;
+
+
+@Command(name = "list", scope = "profile", description = "Lists all profiles")
+@Service
+public class ProfileList implements Action {
+
+    @Option(name = "--hidden", description = "Display hidden profiles")
+    private boolean hidden;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        List<String> ids = new ArrayList<>(profileService.getProfiles());
+        Collections.sort(ids);
+        ShellTable table = new ShellTable();
+        table.column("id");
+        table.column("parents");
+        for (String id : ids) {
+            Profile profile = profileService.getProfile(id);
+            if (profile != null && (hidden || !profile.isHidden())) {
+                String parents = join(" ", profile.getParentIds());
+                table.addRow().addContent(id, parents);
+            }
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/ProfileRename.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/ProfileRename.java 
b/profile/src/main/java/org/apache/karaf/profile/command/ProfileRename.java
new file mode 100644
index 0000000..a638629
--- /dev/null
+++ b/profile/src/main/java/org/apache/karaf/profile/command/ProfileRename.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command;
+
+
+import org.apache.karaf.profile.Profile;
+import org.apache.karaf.profile.ProfileBuilder;
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.profile.command.completers.ProfileCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Command(name = "rename", scope = "profile", description = "Rename the 
specified source profile")
+@Service
+public class ProfileRename implements Action {
+
+    @Option(name = "--version", description = "The profile version to rename. 
Defaults to the current default version.")
+    private String versionId;
+
+    @Option(name = "-f", aliases = "--force", description = "Flag to allow 
replacing the target profile (if exists).")
+    private boolean force;
+
+    @Argument(index = 0, required = true, name = "profile name", description = 
"Name of the profile.")
+    @Completion(ProfileCompleter.class)
+    private String profileName;
+
+    @Argument(index = 1, required = true, name = "new profile name", 
description = "New name of the profile.")
+    private String newName;
+
+    @Reference
+    private ProfileService profileService;
+
+    @Override
+    public Object execute() throws Exception {
+        Profile profile = 
ProfileBuilder.Factory.createFrom(profileService.getProfile(profileName))
+                .identity(newName)
+                .getProfile();
+        profileService.createProfile(profile);
+        profileService.deleteProfile(profileName);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/command/completers/ProfileCompleter.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/command/completers/ProfileCompleter.java
 
b/profile/src/main/java/org/apache/karaf/profile/command/completers/ProfileCompleter.java
new file mode 100644
index 0000000..1c02e5f
--- /dev/null
+++ 
b/profile/src/main/java/org/apache/karaf/profile/command/completers/ProfileCompleter.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.command.completers;
+
+import java.util.List;
+
+import org.apache.karaf.profile.ProfileService;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+@Service
+public class ProfileCompleter implements Completer {
+
+    @Reference
+    private ProfileService profileService;
+
+    public int complete(Session session, CommandLine commandLine, List<String> 
candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            delegate.getStrings().addAll(profileService.getProfiles());
+        } catch (Exception e) {
+            // ignore
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/90cb480d/profile/src/main/java/org/apache/karaf/profile/impl/PlaceholderResolvers.java
----------------------------------------------------------------------
diff --git 
a/profile/src/main/java/org/apache/karaf/profile/impl/PlaceholderResolvers.java 
b/profile/src/main/java/org/apache/karaf/profile/impl/PlaceholderResolvers.java
new file mode 100644
index 0000000..16bf106
--- /dev/null
+++ 
b/profile/src/main/java/org/apache/karaf/profile/impl/PlaceholderResolvers.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.profile.impl;
+
+import java.util.Map;
+
+import org.apache.karaf.profile.PlaceholderResolver;
+import org.osgi.framework.BundleContext;
+
+import static org.apache.karaf.profile.impl.Utils.assertNotNull;
+
+public final class PlaceholderResolvers {
+
+    private PlaceholderResolvers() { }
+
+    public static class PropertyPlaceholderResolver implements 
PlaceholderResolver {
+
+        private final Map<String, String> properties;
+
+        public PropertyPlaceholderResolver(Map<String, String> properties) {
+            this.properties = properties;
+        }
+
+        @Override
+        public String getScheme() {
+            return null;
+        }
+
+        @Override
+        public String resolve(Map<String, Map<String, String>> profile, String 
pid, String key, String value) {
+            return properties.get(value);
+        }
+    }
+
+    public static class ProfilePlaceholderResolver implements 
PlaceholderResolver {
+
+        public final String SCHEME = "profile";
+
+        @Override
+        public String getScheme() {
+            return SCHEME;
+        }
+
+        @Override
+        public String resolve(Map<String, Map<String, String>> profile, String 
pid, String key, String value) {
+            int index = value.indexOf("/");
+            if (index >= 0) {
+                String propertyPid = value.substring(0, index);
+                String propertyKey = value.substring(index + 1);
+                Map<String, String> props = profile.get(propertyPid);
+                if (props != null && props.containsKey(propertyKey)) {
+                    return props.get(propertyKey);
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Substitutes a placeholder with profile:[property file]/[key], with the 
target value.
+     * @return  The target value or the key as is.
+     */
+    public static String substituteProfileProperty(String key, Map<String, 
Map<String, String>> configs) {
+        String pid = key.substring("profile:".length(), key.indexOf("/"));
+        String propertyKey = key.substring(key.indexOf("/") + 1);
+        Map<String, String> targetProps = configs.get(pid);
+        if (targetProps != null && targetProps.containsKey(propertyKey)) {
+            return targetProps.get(propertyKey);
+        } else {
+            return key;
+        }
+    }
+
+    /**
+     * Substitutes bundle property.
+     * @return  The target value or an empty String.
+     */
+    public static String substituteBundleProperty(String key, BundleContext 
bundleContext) {
+        String value = null;
+        if (bundleContext != null) {
+            value = bundleContext.getProperty(key);
+        }
+        if (value == null) {
+            value = System.getProperty(key);
+        }
+        return value != null ? value : "";
+    }
+
+}

Reply via email to