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 : ""; + } + +}
