/* * 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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Pattern; import java.util.zip.CRC32; import org.apache.karaf.profile.Profile; import static org.apache.karaf.profile.impl.Utils.assertNotNull; import static org.apache.karaf.profile.impl.Utils.assertTrue; /** * This immutable profile implementation. */ final class ProfileImpl implements Profile { private static final Pattern ALLOWED_PROFILE_NAMES_PATTERN = Pattern.compile("^[A-Za-z0-9]+[\\.A-Za-z0-9_-]*$"); private final String profileId; private final Map<String, String> attributes; private final List<String> parents = new ArrayList<>(); private final Map<String, byte[]> fileConfigurations = new HashMap<>(); private final Map<String, Map<String, Object>> configurations = new HashMap<>(); private final boolean isOverlay; private int hash; // Only the {@link ProfileBuilder} should construct this ProfileImpl(String profileId, List<String> parents, Map<String, byte[]> fileConfigs, boolean isOverlay) { assertNotNull(profileId, "profileId is null"); assertNotNull(parents, "parents is null"); assertNotNull(fileConfigs, "fileConfigs is null"); assertTrue(ALLOWED_PROFILE_NAMES_PATTERN.matcher(profileId).matches(), "Profile id '" + profileId + "' is invalid. Profile id must be: lower-case letters, numbers, and . _ or - characters"); this.profileId = profileId; this.isOverlay = isOverlay; // Parents this.parents.addAll(parents); // File configurations and derived configurations for (Entry<String, byte[]> entry : fileConfigs.entrySet()) { String fileKey = entry.getKey(); byte[] bytes = entry.getValue(); fileConfigurations.put(fileKey, bytes); if (fileKey.endsWith(Profile.PROPERTIES_SUFFIX)) { String pid = fileKey.substring(0, fileKey.indexOf(Profile.PROPERTIES_SUFFIX)); configurations.put(pid, Collections.unmodifiableMap(Utils.toProperties(bytes))); } } // Attributes are agent configuration with prefix 'attribute.' attributes = getPrefixedMap(ATTRIBUTE_PREFIX); } public String getId() { return profileId; } @Override public Map<String, String> getAttributes() { return Collections.unmodifiableMap(attributes); } @Override public Map<String, String> getConfig() { return getPrefixedMap(CONFIG_PREFIX); } @Override public Map<String, String> getSystem() { return getPrefixedMap(SYSTEM_PREFIX); } private Map<String, String> getPrefixedMap(String prefix) { Map<String, String> map = new HashMap<>(); Map<String, Object> agentConfig = configurations.get(Profile.INTERNAL_PID); if (agentConfig != null) { int prefixLength = prefix.length(); for (Entry<String, Object> entry : agentConfig.entrySet()) { String key = entry.getKey(); if (key.startsWith(prefix)) { map.put(key.substring(prefixLength), entry.getValue().toString()); } } } return map; } @Override public List<String> getLibraries() { return getContainerConfigList(ConfigListType.LIBRARIES); } @Override public List<String> getBundles() { return getContainerConfigList(ConfigListType.BUNDLES); } @Override public List<String> getFeatures() { return getContainerConfigList(ConfigListType.FEATURES); } @Override public List<String> getRepositories() { return getContainerConfigList(ConfigListType.REPOSITORIES); } @Override public List<String> getOverrides() { return getContainerConfigList(ConfigListType.OVERRIDES); } @Override public List<String> getOptionals() { return getContainerConfigList(ConfigListType.OPTIONALS); } @Override public List<String> getParentIds() { return Collections.unmodifiableList(parents); } @Override public boolean isAbstract() { return parseBoolean(getAttributes().get(ABSTRACT)); } @Override public boolean isHidden() { return parseBoolean(getAttributes().get(HIDDEN)); } private Boolean parseBoolean(Object obj) { return obj instanceof Boolean ? (Boolean) obj : Boolean.parseBoolean(obj.toString()); } public boolean isOverlay() { return isOverlay; } @Override public Map<String, byte[]> getFileConfigurations() { return Collections.unmodifiableMap(fileConfigurations); } @Override public Set<String> getConfigurationFileNames() { return Collections.unmodifiableSet(fileConfigurations.keySet()); } @Override public byte[] getFileConfiguration(String fileName) { return fileConfigurations.get(fileName); } public Map<String, Map<String, Object>> getConfigurations() { return Collections.unmodifiableMap(configurations); } @Override public Map<String, Object> getConfiguration(String pid) { Map<String, Object> config = configurations.get(pid); config = config != null ? config : Collections.emptyMap(); return Collections.unmodifiableMap(config); } private List<String> getContainerConfigList(ConfigListType type) { Map<String, Object> containerProps = getConfiguration(Profile.INTERNAL_PID); List<String> rc = new ArrayList<>(); String prefix = type + "."; for (Map.Entry<String, Object> e : containerProps.entrySet()) { if ((e.getKey()).startsWith(prefix)) { rc.add(e.getValue().toString()); } } return rc; } @Override public int hashCode() { if (hash == 0) { CRC32 crc = new CRC32(); crc.update(profileId.getBytes()); List<String> keys = new ArrayList<>(fileConfigurations.keySet()); Collections.sort(keys); for (String key : keys) { crc.update(key.getBytes()); crc.update(fileConfigurations.get(key)); } hash = (int) crc.getValue(); } return hash; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ProfileImpl)) return false; ProfileImpl other = (ProfileImpl) obj; // Equality based on identity return profileId.equals(other.profileId) && fileConfigurations.equals(other.fileConfigurations); } @Override public String toString() { return "Profile[id=" + profileId + ",attrs=" + getAttributes() + "]"; } enum ConfigListType { BUNDLES("bundle"), FEATURES("feature"), LIBRARIES("library"), OPTIONALS("optional"), OVERRIDES("override"), REPOSITORIES("repository"); private String value; ConfigListType(String value) { this.value = value; } public String toString() { return value; } } }