reference\:file\:../lib/jersey-server-1.17.jar@2:start
# Netconf startup configuration
-netconf.tcp.address=127.0.0.1
-netconf.tcp.port=8383
-netconf.tcp.client.address=127.0.0.1
-netconf.tcp.client.port=8383
+# Netconf tcp address:port is optional with default value 127.0.0.1:8383
+#netconf.tcp.address=127.0.0.1
+#netconf.tcp.port=8384
+
+#netconf.tcp.client.address=127.0.0.1
+#netconf.tcp.client.port=8384
netconf.ssh.address=0.0.0.0
netconf.ssh.port=1830
*/
package org.opendaylight.controller.netconf.impl.osgi;
-import io.netty.channel.nio.NioEventLoopGroup;
-import io.netty.util.HashedWheelTimer;
+import java.lang.management.ManagementFactory;
+import java.net.InetSocketAddress;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.TimeUnit;
+
import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.lang.management.ManagementFactory;
-import java.net.InetSocketAddress;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.concurrent.TimeUnit;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.HashedWheelTimer;
public class NetconfImplActivator implements BundleActivator {
private ServiceRegistration<NetconfMonitoringService> regMonitoring;
@Override
- public void start(final BundleContext context) {
- InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfAddress(context,
- "TCP is not configured, netconf not available.", false);
-
- NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
+ public void start(final BundleContext context) {
+ final InetSocketAddress address = NetconfConfigUtil.extractTCPNetconfServerAddress(context,
+ NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
+ final NetconfOperationServiceFactoryListenerImpl factoriesListener = new NetconfOperationServiceFactoryListenerImpl();
startOperationServiceFactoryTracker(context, factoriesListener);
- SessionIdProvider idProvider = new SessionIdProvider();
+ final SessionIdProvider idProvider = new SessionIdProvider();
timer = new HashedWheelTimer();
- long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
+ long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
commitNot = new DefaultCommitNotificationProducer(ManagementFactory.getPlatformMBeanServer());
NetconfServerDispatcher.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcher.ServerChannelInitializer(
serverNegotiatorFactory);
- NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
+
+ NetconfServerDispatcher dispatch = new NetconfServerDispatcher(serverChannelInitializer, eventLoopGroup,
+ eventLoopGroup);
logger.info("Starting TCP netconf server at {}", address);
dispatch.createServer(address);
context.registerService(NetconfOperationProvider.class, factoriesListener, null);
-
}
- private void startOperationServiceFactoryTracker(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+ private void startOperationServiceFactoryTracker(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
factoriesTracker = new NetconfOperationServiceFactoryTracker(context, factoriesListener);
factoriesTracker.open();
}
- private NetconfMonitoringServiceImpl startMonitoringService(BundleContext context, NetconfOperationServiceFactoryListenerImpl factoriesListener) {
- NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
- Dictionary<String, ?> dic = new Hashtable<>();
+ private NetconfMonitoringServiceImpl startMonitoringService(final BundleContext context, final NetconfOperationServiceFactoryListenerImpl factoriesListener) {
+ final NetconfMonitoringServiceImpl netconfMonitoringServiceImpl = new NetconfMonitoringServiceImpl(factoriesListener);
+ final Dictionary<String, ?> dic = new Hashtable<>();
regMonitoring = context.registerService(NetconfMonitoringService.class, netconfMonitoringServiceImpl, dic);
return netconfMonitoringServiceImpl;
package org.opendaylight.controller.netconf.ssh.authentication;
+import java.security.NoSuchAlgorithmException;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.openssl.PEMWriter;
import org.slf4j.Logger;
private static final Logger logger = LoggerFactory.getLogger(PEMGenerator.class);
private static final int KEY_SIZE = 4096;
- public static String generateTo(File privateFile) throws Exception {
+ public static String generateTo(File privateFile) throws IOException, NoSuchAlgorithmException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
SecureRandom sr = new SecureRandom();
keyGen.initialize(KEY_SIZE, sr);
*/
package org.opendaylight.controller.netconf.ssh.osgi;
-import com.google.common.base.Optional;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
-import java.util.ArrayList;
-import java.util.List;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.opendaylight.controller.netconf.ssh.NetconfSSHServer;
import org.opendaylight.controller.netconf.ssh.authentication.AuthProvider;
import org.opendaylight.controller.netconf.ssh.authentication.PEMGenerator;
import org.opendaylight.controller.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.controller.sal.authorization.UserLevel;
import org.opendaylight.controller.usermanager.IUserManager;
-import org.opendaylight.controller.usermanager.UserConfig;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static com.google.common.base.Preconditions.checkNotNull;
/**
* Activator for netconf SSH bundle which creates SSH bridge between netconf client and netconf server. Activator
private NetconfSSHServer server;
private static final Logger logger = LoggerFactory.getLogger(NetconfSSHActivator.class);
- private static final String EXCEPTION_MESSAGE = "Netconf ssh bridge is not available.";
private IUserManager iUserManager;
private BundleContext context = null;
- private Optional<String> defaultPassword;
- private Optional<String> defaultUser;
private ServiceTrackerCustomizer<IUserManager, IUserManager> customizer = new ServiceTrackerCustomizer<IUserManager, IUserManager>(){
@Override
- public IUserManager addingService(ServiceReference<IUserManager> reference) {
+ public IUserManager addingService(final ServiceReference<IUserManager> reference) {
logger.trace("Service {} added, let there be SSH bridge.", reference);
iUserManager = context.getService(reference);
try {
onUserManagerFound(iUserManager);
- } catch (Exception e) {
+ } catch (final Exception e) {
logger.trace("Can't start SSH server due to {}",e);
}
return iUserManager;
}
@Override
- public void modifiedService(ServiceReference<IUserManager> reference, IUserManager service) {
+ public void modifiedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
logger.trace("Replacing modified service {} in netconf SSH.", reference);
server.addUserManagerService(service);
}
@Override
- public void removedService(ServiceReference<IUserManager> reference, IUserManager service) {
+ public void removedService(final ServiceReference<IUserManager> reference, final IUserManager service) {
logger.trace("Removing service {} from netconf SSH. " +
"SSH won't authenticate users until IUserManager service will be started.", reference);
removeUserManagerService();
@Override
- public void start(BundleContext context) {
+ public void start(final BundleContext context) {
this.context = context;
listenForManagerService();
}
@Override
public void stop(BundleContext context) throws IOException {
- if (this.defaultUser.isPresent()){
- this.iUserManager.removeLocalUser(this.defaultUser.get());
- }
if (server != null){
server.stop();
logger.trace("Netconf SSH bridge is down ...");
}
}
- private void startSSHServer() throws IllegalStateException, IOException {
+ private void startSSHServer() throws IOException {
checkNotNull(this.iUserManager, "No user manager service available.");
logger.trace("Starting netconf SSH bridge.");
- Optional<InetSocketAddress> sshSocketAddressOptional = NetconfConfigUtil.extractSSHNetconfAddress(context, EXCEPTION_MESSAGE);
- InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfAddress(context,
- EXCEPTION_MESSAGE, true);
+ final InetSocketAddress sshSocketAddress = NetconfConfigUtil.extractSSHNetconfAddress(context,
+ NetconfConfigUtil.DEFAULT_NETCONF_SSH_ADDRESS);
+ final InetSocketAddress tcpSocketAddress = NetconfConfigUtil.extractTCPNetconfClientAddress(context,
+ NetconfConfigUtil.DEFAULT_NETCONF_TCP_ADDRESS);
- if (sshSocketAddressOptional.isPresent()){
- String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(context));
- if (path.equals("")){
- throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file.");
- }
+ String path = FilenameUtils.separatorsToSystem(NetconfConfigUtil.getPrivateKeyPath(context));
- File privateKeyFile = new File(path);
- String privateKeyPEMString = null;
- if (privateKeyFile.exists() == false) {
- try {
- privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
- } catch (Exception e) {
- logger.error("Exception occurred while generating PEM string {}",e);
- }
- } else {
- // read from file
- try (FileInputStream fis = new FileInputStream(path)) {
- privateKeyPEMString = IOUtils.toString(fis);
- } catch (IOException e) {
- logger.error("Error reading RSA key from file '{}'", path);
- throw new IllegalStateException("Error reading RSA key from file " + path);
- }
- }
- AuthProvider authProvider = null;
+ if (path.isEmpty()) {
+ throw new IllegalStateException("Missing netconf.ssh.pk.path key in configuration file.");
+ }
+
+ final File privateKeyFile = new File(path);
+ final String privateKeyPEMString;
+ if (privateKeyFile.exists() == false) {
+ // generate & save to file
try {
- this.defaultPassword = NetconfConfigUtil.getSSHDefaultPassword(context);
- this.defaultUser = NetconfConfigUtil.getSSHDefaultUser(context);
- // Since there is no user data store yet (ldap, ...) this adds default user/password to UserManager
- // if these parameters are set in netconf configuration file.
- if (defaultUser.isPresent() &&
- defaultPassword.isPresent()){
- logger.trace(String.format("Default username and password for netconf ssh bridge found. Adding user %s to user manager.",defaultUser.get()));
- List<String> roles = new ArrayList<String>(1);
- roles.add(UserLevel.SYSTEMADMIN.toString());
- iUserManager.addLocalUser(new UserConfig(defaultUser.get(), defaultPassword.get(), roles));
- }
- authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
+ privateKeyPEMString = PEMGenerator.generateTo(privateKeyFile);
} catch (Exception e) {
- logger.error("Error instantiating AuthProvider {}",e);
+ logger.error("Exception occurred while generating PEM string {}", e);
+ throw new IllegalStateException("Error generating RSA key from file " + path);
}
- this.server = NetconfSSHServer.start(sshSocketAddressOptional.get().getPort(),tcpSocketAddress,authProvider);
-
- Thread serverThread = new Thread(server,"netconf SSH server thread");
- serverThread.setDaemon(true);
- serverThread.start();
- logger.trace("Netconf SSH bridge up and running.");
} else {
- logger.trace("No valid connection configuration for SSH bridge found.");
- throw new IllegalStateException("No valid connection configuration for SSH bridge found.");
+ // read from file
+ try (FileInputStream fis = new FileInputStream(path)) {
+ privateKeyPEMString = IOUtils.toString(fis);
+ } catch (final IOException e) {
+ logger.error("Error reading RSA key from file '{}'", path);
+ throw new IOException("Error reading RSA key from file " + path, e);
+ }
}
+ final AuthProvider authProvider = new AuthProvider(iUserManager, privateKeyPEMString);
+ this.server = NetconfSSHServer.start(sshSocketAddress.getPort(), tcpSocketAddress, authProvider);
+
+ final Thread serverThread = new Thread(server, "netconf SSH server thread");
+ serverThread.setDaemon(true);
+ serverThread.start();
+ logger.trace("Netconf SSH bridge up and running.");
}
- private void onUserManagerFound(IUserManager userManager) throws IOException {
+
+ private void onUserManagerFound(final IUserManager userManager) throws Exception{
if (server!=null && server.isUp()){
server.addUserManagerService(userManager);
} else {
this.server.removeUserManagerService();
}
private void listenForManagerService(){
- ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer);
+ final ServiceTracker<IUserManager, IUserManager> listenerTracker = new ServiceTracker<>(context, IUserManager.class,customizer);
listenerTracker.open();
}
}
package org.opendaylight.controller.netconf.util.osgi;
import com.google.common.base.Optional;
-import com.google.common.base.Strings;
-import java.net.InetSocketAddress;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.InetSocketAddress;
public final class NetconfConfigUtil {
private static final Logger logger = LoggerFactory.getLogger(NetconfConfigUtil.class);
+ public static final InetSocketAddress DEFAULT_NETCONF_TCP_ADDRESS
+ = new InetSocketAddress("127.0.0.1", 8383);
+ public static final InetSocketAddress DEFAULT_NETCONF_SSH_ADDRESS
+ = new InetSocketAddress("0.0.0.0", 1830);
+
private static final String PREFIX_PROP = "netconf.";
- private NetconfConfigUtil() {}
+ private NetconfConfigUtil() {
+ }
private enum InfixProp {
tcp, ssh
private static final String ADDRESS_SUFFIX_PROP = ".address";
private static final String CLIENT_PROP = ".client";
private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
- private static final String SSH_DEFAULT_USER = ".default.user";
- private static final String SSH_DEFAULT_PASSWORD = ".default.password";
private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
private static final long DEFAULT_TIMEOUT_MILLIS = 5000;
- public static long extractTimeoutMillis(BundleContext bundleContext) {
- String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
- String timeoutString = bundleContext.getProperty(key);
+ public static long extractTimeoutMillis(final BundleContext bundleContext) {
+ final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
+ final String timeoutString = bundleContext.getProperty(key);
if (timeoutString == null || timeoutString.length() == 0) {
return DEFAULT_TIMEOUT_MILLIS;
}
try {
return Long.parseLong(timeoutString);
- }catch(NumberFormatException e) {
+ } catch (final NumberFormatException e) {
logger.warn("Cannot parse {} property: {}, using defaults", key, timeoutString, e);
return DEFAULT_TIMEOUT_MILLIS;
}
}
- public static InetSocketAddress extractTCPNetconfAddress(BundleContext context, String exceptionMessageIfNotFound, boolean forClient) {
-
- Optional<InetSocketAddress> inetSocketAddressOptional = extractSomeNetconfAddress(context, InfixProp.tcp, exceptionMessageIfNotFound, forClient);
-
- if (!inetSocketAddressOptional.isPresent()) {
- throw new IllegalStateException("Netconf tcp address not found." + exceptionMessageIfNotFound);
- }
- InetSocketAddress inetSocketAddress = inetSocketAddressOptional.get();
- if (inetSocketAddress.getAddress().isAnyLocalAddress()) {
+ public static InetSocketAddress extractTCPNetconfServerAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
+ final Optional<InetSocketAddress> extracted = extractNetconfServerAddress(context, InfixProp.tcp);
+ final InetSocketAddress netconfTcpAddress = getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
+ logger.debug("Using {} as netconf tcp address", netconfTcpAddress);
+ if (netconfTcpAddress.getAddress().isAnyLocalAddress()) {
logger.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
"Consider changing {} to 127.0.0.1", PREFIX_PROP + InfixProp.tcp + ADDRESS_SUFFIX_PROP);
}
- return inetSocketAddress;
+ return netconfTcpAddress;
}
- public static Optional<InetSocketAddress> extractSSHNetconfAddress(BundleContext context, String exceptionMessage) {
- return extractSomeNetconfAddress(context, InfixProp.ssh, exceptionMessage, false);
+ public static InetSocketAddress extractTCPNetconfClientAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
+ final Optional<InetSocketAddress> extracted = extractNetconfClientAddress(context, InfixProp.tcp);
+ return getNetconfAddress(defaultAddress, extracted, InfixProp.tcp);
}
- public static String getPrivateKeyPath(BundleContext context){
- return getPropertyValue(context,PREFIX_PROP + InfixProp.ssh +PRIVATE_KEY_PATH_PROP);
+ /**
+ * Get extracted address or default.
+ *
+ * @throws java.lang.IllegalStateException if neither address is present.
+ */
+ private static InetSocketAddress getNetconfAddress(final InetSocketAddress defaultAddress, Optional<InetSocketAddress> extractedAddress, InfixProp infix) {
+ InetSocketAddress inetSocketAddress;
+
+ if (extractedAddress.isPresent() == false) {
+ logger.debug("Netconf {} address not found, falling back to default {}", infix, defaultAddress);
+
+ if (defaultAddress == null) {
+ logger.warn("Netconf {} address not found, default address not provided", infix);
+ throw new IllegalStateException("Netconf " + infix + " address not found, default address not provided");
+ }
+ inetSocketAddress = defaultAddress;
+ } else {
+ inetSocketAddress = extractedAddress.get();
+ }
+
+ return inetSocketAddress;
}
- public static Optional<String> getSSHDefaultUser(BundleContext context){
- return getOptionalPropertyValue(context,PREFIX_PROP + InfixProp.ssh +SSH_DEFAULT_USER);
+
+ public static InetSocketAddress extractSSHNetconfAddress(final BundleContext context, final InetSocketAddress defaultAddress) {
+ Optional<InetSocketAddress> extractedAddress = extractNetconfServerAddress(context, InfixProp.ssh);
+ InetSocketAddress netconfSSHAddress = getNetconfAddress(defaultAddress, extractedAddress, InfixProp.ssh);
+ logger.debug("Using {} as netconf SSH address", netconfSSHAddress);
+ return netconfSSHAddress;
}
- public static Optional<String> getSSHDefaultPassword(BundleContext context){
- return getOptionalPropertyValue(context,PREFIX_PROP + InfixProp.ssh +SSH_DEFAULT_PASSWORD);
+
+ public static String getPrivateKeyPath(final BundleContext context) {
+ return getPropertyValue(context, PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP);
}
- private static String getPropertyValue(BundleContext context, String propertyName){
- String propertyValue = context.getProperty(propertyName);
- if (propertyValue == null){
- throw new IllegalStateException("Cannot find initial property with name '"+propertyName+"'");
+ private static String getPropertyValue(final BundleContext context, final String propertyName) {
+ final String propertyValue = context.getProperty(propertyName);
+ if (propertyValue == null) {
+ throw new IllegalStateException("Cannot find initial property with name '" + propertyName + "'");
}
return propertyValue;
}
- private static Optional<String> getOptionalPropertyValue(BundleContext context, String propertyName){
- String propertyValue = context.getProperty(propertyName);
- if (Strings.isNullOrEmpty(propertyValue)){
- return Optional.absent();
- }
- return Optional.fromNullable(propertyValue);
- }
+
/**
- * @param context
- * from which properties are being read.
- * @param infixProp
- * either tcp or ssh
- * @return value if address and port are valid.
- * @throws IllegalStateException
- * if address or port are invalid, or configuration is missing
+ * @param context from which properties are being read.
+ * @param infixProp either tcp or ssh
+ * @return value if address and port are present and valid, Optional.absent otherwise.
+ * @throws IllegalStateException if address or port are invalid, or configuration is missing
*/
- private static Optional<InetSocketAddress> extractSomeNetconfAddress(BundleContext context,
- InfixProp infixProp,
- String exceptionMessage,
- boolean client) {
- String address = "";
- if (client) {
- address = context.getProperty(PREFIX_PROP + infixProp + CLIENT_PROP + ADDRESS_SUFFIX_PROP);
- }
- if (address == null || address.equals("")){
- address = context.getProperty(PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP);
- }
- if (address == null || address.equals("")) {
- throw new IllegalStateException("Cannot find initial netconf configuration for parameter "
- +PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP
- +" in config.ini. "+exceptionMessage);
- }
- String portKey = "";
- if (client) {
- portKey = PREFIX_PROP + infixProp + CLIENT_PROP + PORT_SUFFIX_PROP;
+ private static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
+ final InfixProp infixProp) {
+
+ final Optional<String> address = getProperty(context, PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP);
+ final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
+
+ if (address.isPresent() && port.isPresent()) {
+ try {
+ return Optional.of(parseAddress(address, port));
+ } catch (final RuntimeException e) {
+ logger.warn("Unable to parse {} netconf address from {}:{}, fallback to default",
+ infixProp, address, port, e);
+ }
}
- if (portKey == null || portKey.equals("")){
- portKey = PREFIX_PROP + infixProp + PORT_SUFFIX_PROP;
+ return Optional.absent();
+ }
+
+ private static InetSocketAddress parseAddress(final Optional<String> address, final Optional<String> port) {
+ final int portNumber = Integer.valueOf(port.get());
+ return new InetSocketAddress(address.get(), portNumber);
+ }
+
+ private static Optional<InetSocketAddress> extractNetconfClientAddress(final BundleContext context,
+ final InfixProp infixProp) {
+ final Optional<String> address = getProperty(context,
+ PREFIX_PROP + infixProp + CLIENT_PROP + ADDRESS_SUFFIX_PROP);
+ final Optional<String> port = getProperty(context,
+ PREFIX_PROP + infixProp + CLIENT_PROP + PORT_SUFFIX_PROP);
+
+ if (address.isPresent() && port.isPresent()) {
+ try {
+ return Optional.of(parseAddress(address, port));
+ } catch (final RuntimeException e) {
+ logger.warn("Unable to parse client {} netconf address from {}:{}, fallback to server address",
+ infixProp, address, port, e);
+ }
}
- String portString = context.getProperty(portKey);
- checkNotNull(portString, "Netconf port must be specified in properties file with " + portKey);
- try {
- int port = Integer.valueOf(portString);
- return Optional.of(new InetSocketAddress(address, port));
- } catch (RuntimeException e) {
- throw new IllegalStateException("Cannot create " + infixProp + " netconf address from address:" + address
- + " and port:" + portString, e);
+ return extractNetconfServerAddress(context, infixProp);
+ }
+
+ private static Optional<String> getProperty(final BundleContext context, final String propKey) {
+ String value = context.getProperty(propKey);
+ if (value != null && value.isEmpty()) {
+ value = null;
}
+ return Optional.fromNullable(value);
}
}