package com.sixsq.slipstream.connector; /* * +=================================================================+ * SlipStream Server (WAR) * ===== * Copyright (C) 2013 SixSq Sarl (sixsq.com) * ===== * 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. * -=================================================================- */ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.sixsq.slipstream.configuration.Configuration; import com.sixsq.slipstream.exceptions.ConfigurationException; import com.sixsq.slipstream.exceptions.SlipStreamRuntimeException; import com.sixsq.slipstream.exceptions.ValidationException; import com.sixsq.slipstream.persistence.Parameter; import com.sixsq.slipstream.persistence.ServiceCatalog; import com.sixsq.slipstream.persistence.ServiceCatalogs; import com.sixsq.slipstream.persistence.ServiceConfiguration.RequiredParameters; import com.sixsq.slipstream.persistence.User; public class ConnectorFactory { private static final String CONNECTOR_CLASS_SEPARATOR = ","; private static Map<String, Connector> connectors = null; public static Connector getCurrentConnector(User user) throws ConfigurationException, ValidationException { String cloudServiceName = getDefaultCloudServiceName(user); if ("".equals(cloudServiceName)) { // user not configured, we take the first connector cloudServiceName = getConnectors().entrySet().iterator().next().getKey(); } if (!Parameter.hasValueSet(cloudServiceName)) { throwIncompleteUserCloudConfiguration(user); } return getConnector(cloudServiceName); } protected static void throwIncompleteUserCloudConfiguration(User user) throws ValidationException { throw new ValidationException(incompleteCloudConfigurationErrorMessage(user)); } public static String incompleteCloudConfigurationErrorMessage(User user) { return "Incomplete cloud configuration. Consider editing your <a href='" + "/user/" + user .getName() + "?edit=true'>user account</a>"; } public static Connector getConnector(String cloudServiceName) throws ConfigurationException, ValidationException { Connector connector = getConnectors().get(cloudServiceName); if (connector == null) { throw new ValidationException("Failed to load cloud connector: " + cloudServiceName); } return connector.copy(); } public static Connector getConnector(String cloudServiceName, User user) throws ConfigurationException, ValidationException { if (CloudService.isDefaultCloudService(cloudServiceName)) { cloudServiceName = getDefaultCloudServiceName(user); if ("".equals(cloudServiceName)) { throw new ValidationException("Missing default cloud in user"); } } return getConnector(cloudServiceName); } public static Connector instantiateConnectorFromName(String connectorClassName) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException { return (Connector) Class.forName(connectorClassName).getConstructor().newInstance(); } public static String getDefaultCloudServiceName(User user) { return user.getDefaultCloudService(); } @SuppressWarnings("unused") private static Connector loadConnector(String cloudServiceName) throws ConfigurationException { return loadConnector(cloudServiceName, null); } private static Connector loadConnector(String cloudServiceName, String instanceName) throws ConfigurationException { try { DiscoverableConnectorService svc = DiscoverableConnectorServiceLoader.getConnectorService(cloudServiceName); if (svc != null) { return svc.getInstance(instanceName); } else { throw new SlipStreamRuntimeException( "cannot load cloud connector for " + cloudServiceName + " using key " + convertClassNameToServiceName(cloudServiceName) ); } } catch (Exception e) { throw new SlipStreamRuntimeException(e.getClass().getName() + " " + e.getMessage()); } } public static void resetConnectors() { connectors = null; } public static void setConnectors(Map<String, Connector> inputConnectors) { Map<String, Connector> connectors = new HashMap<String, Connector>(); connectors.putAll(inputConnectors); ConnectorFactory.connectors = Collections.unmodifiableMap(connectors); } public static Map<String, Connector> getConnectors(String[] classNames) throws ConfigurationException { if (connectors == null) { initializeConnectors(classNames); } return connectors; } public static void initializeConnectors(String[] classNames) throws ConfigurationException { Map<String, Connector> connectors = new HashMap<String, Connector>(); Map<String, String> instanceNames = processConnectorInstanceConfig(classNames); for (Map.Entry<String, String> entry : instanceNames.entrySet()) { String instanceName = entry.getKey(); String cloudServiceName = entry.getValue(); Connector connector = loadConnector(cloudServiceName, instanceName); connectors.put(instanceName, connector); } setConnectors(connectors); updateServiceCatalog(connectors.keySet()); } private static Map<String, String> processConnectorInstanceConfig(String[] instances) { Map<String, String> instanceMap = new HashMap<String, String>(); for (String c : instances) { // A connector ID (either cloud service name or class name) with an optional instance name. String[] namePair = c.split(":"); boolean isNamed = namePair.length > 1; // The loader will maintain compatibility for configurations with raw class names // rather than just the service names. String cloudServiceName = (isNamed) ? namePair[1].trim() : namePair[0].trim(); // Default to cloud service name if no instance name is given. String instanceName = (isNamed) ? namePair[0].trim() : convertClassNameToServiceName(cloudServiceName); instanceMap.put(instanceName, cloudServiceName); } return instanceMap; } // If the argument looks to be a class name, then derive the cloud service name from the class name. The cloud // service name will be the penultimate value when split on periods. If there are no periods in the value, // then just return the value itself. public static String convertClassNameToServiceName(String configConnectorName) { String[] elements = configConnectorName.split("\\."); if (elements.length > 1) { String name = elements[elements.length - 2].toLowerCase(); // Special case for the EC2 connector which doesn't follow the usual naming scheme of // residing is a directory named after the cloud service name. (Directory is 'aws' but // the cloud service name is 'ec2'.) if ("aws".equals(name)) { return "ec2"; } else { return name; } } else { return configConnectorName.toLowerCase(); } } private static void updateServiceCatalog(Set<String> cloudServiceNames) { ServiceCatalogs scs = new ServiceCatalogs(); scs.loadAll(); for (ServiceCatalog sc : scs.getList()) { if (!cloudServiceNames.contains(sc.getCloud())) { sc.remove(); } } for (String cloud : cloudServiceNames) { boolean foundIt = false; scs.loadAll(); for (ServiceCatalog sc : scs.getList()) { if (sc.getCloud().equals(cloud)) { foundIt = true; break; } } if (!foundIt) { ServiceCatalog sc = new ServiceCatalog(cloud); sc = sc.store(); scs.getList().add(sc); } } } public static Map<String, Connector> getConnectors() throws ConfigurationException, ValidationException { return getConnectors(getConnectorClassNames()); } public static String[] getConnectorClassNames() throws ConfigurationException, ValidationException { String connectorsClassNames = Configuration.getInstance().getRequiredProperty( RequiredParameters.CLOUD_CONNECTOR_CLASS.getName()); return splitConnectorClassNames(connectorsClassNames); } public static String[] splitConnectorClassNames(String connectorsClassNames) { if (connectorsClassNames == null || connectorsClassNames.trim().isEmpty()) { return new String[0]; } return connectorsClassNames.split(CONNECTOR_CLASS_SEPARATOR); } public static String assembleConnectorClassString(String[] connectorsClassNames) { StringBuilder sb = new StringBuilder(); for(String c : connectorsClassNames) { sb.append(c); sb.append(CONNECTOR_CLASS_SEPARATOR); } return sb.toString(); } public static List<String> getCloudServiceNamesList() throws ConfigurationException, ValidationException { return new ArrayList<String>(getConnectors().keySet()); } public static String[] getCloudServiceNames() throws ConfigurationException, ValidationException { List<String> names = getCloudServiceNamesList(); return names.toArray(new String[names.size()]); } }