/* * Copyright 2014-2016 CyberVision, Inc. * * Licensed 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.kaaproject.kaa.client; import static org.kaaproject.kaa.client.util.Utils.isBlank; import org.apache.commons.compress.utils.Charsets; import org.kaaproject.kaa.client.channel.GenericTransportInfo; import org.kaaproject.kaa.client.channel.ServerType; import org.kaaproject.kaa.client.channel.TransportConnectionInfo; import org.kaaproject.kaa.client.channel.TransportProtocolId; import org.kaaproject.kaa.client.util.Base64; import org.kaaproject.kaa.common.endpoint.gen.ProtocolMetaData; import org.kaaproject.kaa.common.endpoint.gen.ProtocolVersionPair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; /** * Service class to store base endpoint configuration. */ public class KaaClientProperties extends Properties { public static final String KAA_CLIENT_PROPERTIES_FILE = "kaaClientPropertiesFile"; public static final String BUILD_VERSION = "build.version"; public static final String BUILD_COMMIT_HASH = "build.commit_hash"; public static final String TRANSPORT_POLL_DELAY = "transport.poll.initial_delay"; public static final String TRANSPORT_POLL_PERIOD = "transport.poll.period"; public static final String TRANSPORT_POLL_UNIT = "transport.poll.unit"; public static final String BOOTSTRAP_SERVERS = "transport.bootstrap.servers"; public static final String CONFIG_DATA_DEFAULT = "config.data.default"; public static final String CONFIG_SCHEMA_DEFAULT = "config.schema.default"; public static final String SDK_TOKEN = "sdk_token"; public static final String WORKING_DIR_PROPERTY = "kaa.work_dir"; public static final String FILE_SEPARATOR = File.separator; public static final String WORKING_DIR_DEFAULT = "." + FILE_SEPARATOR; public static final String STATE_FILE_NAME_DEFAULT = "state.properties"; public static final String CLIENT_PRIVATE_KEY_NAME_DEFAULT = "key.private"; public static final String CLIENT_PUBLIC_KEY_NAME_DEFAULT = "key.public"; public static final String STATE_FILE_NAME_PROPERTY = "state.file_name"; public static final String CLIENT_PRIVATE_KEY_FILE_NAME_PROPERTY = "keys.private_name"; public static final String CLIENT_PUBLIC_KEY_FILE_NAME_PROPERTY = "keys.public_name"; private static final Logger LOG = LoggerFactory.getLogger(KaaClientProperties.class); private static final String DEFAULT_CLIENT_PROPERTIES = "client.properties"; private static final long serialVersionUID = 8793954229852581418L; private static final String PROPERTIES_HASH_ALGORITHM = "SHA"; private Base64 base64; private byte[] propertiesHash; public KaaClientProperties() throws IOException { super(loadProperties(null)); } public KaaClientProperties(String propertiesLocation) throws IOException { super(loadProperties(propertiesLocation)); } public KaaClientProperties(Properties properties) { super(properties); } private static Properties loadProperties(String propsLocation) throws IOException { Properties properties = null; String propertiesLocation = isBlank(propsLocation) ? DEFAULT_CLIENT_PROPERTIES : propsLocation; if (System.getProperty(KAA_CLIENT_PROPERTIES_FILE) != null) { propertiesLocation = System.getProperty(KAA_CLIENT_PROPERTIES_FILE); } properties = new Properties(); ClassLoader classLoader = Kaa.class.getClassLoader(); properties.load(classLoader.getResourceAsStream(propertiesLocation)); return properties; } private static void checkNotBlankProperty(String fileName, String errorMessage) { if (isBlank(fileName)) { throw new IllegalArgumentException(errorMessage); } } /** * Calculates a hash for SDK properties. * * @return hash for SDK properties */ public byte[] getPropertiesHash() { if (propertiesHash == null) { try { MessageDigest digest = MessageDigest.getInstance(PROPERTIES_HASH_ALGORITHM); updateDigest(digest, TRANSPORT_POLL_DELAY); updateDigest(digest, TRANSPORT_POLL_PERIOD); updateDigest(digest, TRANSPORT_POLL_UNIT); updateDigest(digest, BOOTSTRAP_SERVERS); updateDigest(digest, CONFIG_DATA_DEFAULT); updateDigest(digest, CONFIG_SCHEMA_DEFAULT); updateDigest(digest, SDK_TOKEN); propertiesHash = digest.digest(); } catch (NoSuchAlgorithmException ex) { LOG.warn("Failed to calculate hash for SDK properties: {}", ex); } } return propertiesHash; } private void updateDigest(MessageDigest digest, String propertyName) { String value = getProperty(propertyName); if (value != null) { digest.update(value.getBytes()); } } public String getBuildVersion() { return getProperty(BUILD_VERSION); } public String getCommitHash() { return getProperty(BUILD_COMMIT_HASH); } public Map<TransportProtocolId, List<TransportConnectionInfo>> getBootstrapServers() throws InvalidKeySpecException, NoSuchAlgorithmException { return parseBootstrapServers(getProperty(KaaClientProperties.BOOTSTRAP_SERVERS)); } public String getSdkToken() { return getProperty(KaaClientProperties.SDK_TOKEN); } public Integer getPollDelay() { return Integer.valueOf(getProperty(KaaClientProperties.TRANSPORT_POLL_DELAY)); } public Integer getPollPeriod() { return Integer.valueOf(getProperty(KaaClientProperties.TRANSPORT_POLL_PERIOD)); } public TimeUnit getPollUnit() { return TimeUnit.valueOf(getProperty(KaaClientProperties.TRANSPORT_POLL_UNIT)); } private Map<TransportProtocolId, List<TransportConnectionInfo>> parseBootstrapServers( String serversStr) throws InvalidKeySpecException, NoSuchAlgorithmException { Map<TransportProtocolId, List<TransportConnectionInfo>> servers = new HashMap<>(); String[] serversSplit = serversStr.split(";"); for (String server : serversSplit) { if (server != null && !server.trim().isEmpty()) { String[] tokens = server.split(":"); ProtocolMetaData md = new ProtocolMetaData(); md.setAccessPointId(Integer.valueOf(tokens[0])); md.setProtocolVersionInfo(new ProtocolVersionPair(Integer.valueOf(tokens[1]), Integer.valueOf(tokens[2]))); md.setConnectionInfo(ByteBuffer.wrap(getBase64().decodeBase64(tokens[3]))); TransportProtocolId key = new TransportProtocolId(md.getProtocolVersionInfo().getId(), md.getProtocolVersionInfo().getVersion()); List<TransportConnectionInfo> serverList = servers.get(key); if (serverList == null) { serverList = new ArrayList<TransportConnectionInfo>(); servers.put(key, serverList); } serverList.add(new GenericTransportInfo(ServerType.BOOTSTRAP, md)); } } return servers; } public byte[] getDefaultConfigData() { String config = getProperty(KaaClientProperties.CONFIG_DATA_DEFAULT); return (config != null) ? getBase64().decodeBase64(config.getBytes(Charsets.UTF_8)) : null; } public byte[] getDefaultConfigSchema() { String schema = getProperty(KaaClientProperties.CONFIG_SCHEMA_DEFAULT); return (schema != null) ? schema.getBytes(Charsets.UTF_8) : null; } public Base64 getBase64() { return base64; } public void setBase64(Base64 base64) { this.base64 = base64; } public String getWorkingDirectory() { String workingDir = getProperty(WORKING_DIR_PROPERTY); return isBlank(workingDir) ? WORKING_DIR_DEFAULT : checkDir(workingDir); } public void setWorkingDirectory(String workDir) { checkNotBlankProperty(workDir, "Working directory folder name couldn't be blank"); setProperty(WORKING_DIR_PROPERTY, checkDir(workDir)); } private String checkDir(String workDir) { return workDir.endsWith(FILE_SEPARATOR) ? workDir : workDir + FILE_SEPARATOR; } public String getStateFileName() { String stateFileName = getProperty(STATE_FILE_NAME_PROPERTY); return isBlank(stateFileName) ? STATE_FILE_NAME_DEFAULT : stateFileName; } public void setStateFileName(String fileName) { checkNotBlankProperty(fileName, "State file name couldn't be blank"); setProperty(STATE_FILE_NAME_PROPERTY, fileName); } public String getStateFileFullName() { return getWorkingDirectory() + getStateFileName(); } public String getPublicKeyFileName() { String privateKeyName = getProperty(CLIENT_PUBLIC_KEY_FILE_NAME_PROPERTY); return isBlank(privateKeyName) ? CLIENT_PRIVATE_KEY_NAME_DEFAULT : privateKeyName; } public void setPublicKeyFileName(String fileName) { checkNotBlankProperty(fileName, "Public key file name couldn't be blank"); setProperty(CLIENT_PUBLIC_KEY_FILE_NAME_PROPERTY, fileName); } public String getPublicKeyFileFullName() { return getWorkingDirectory() + getPublicKeyFileName(); } public String getPrivateKeyFileName() { String publicKeyName = getProperty(CLIENT_PRIVATE_KEY_FILE_NAME_PROPERTY); return isBlank(publicKeyName) ? CLIENT_PUBLIC_KEY_NAME_DEFAULT : publicKeyName; } public void setPrivateKeyFileName(String fileName) { checkNotBlankProperty(fileName, "Private key file name couldn't be blank"); setProperty(CLIENT_PRIVATE_KEY_FILE_NAME_PROPERTY, fileName); } public String getPrivateKeyFileFullName() { return getWorkingDirectory() + getPrivateKeyFileName(); } }