All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.dataconnection.impl.DataConnectionServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * 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 com.hazelcast.dataconnection.impl;

import com.hazelcast.client.impl.protocol.util.PropertiesUtil;
import com.hazelcast.config.DataConnectionConfig;
import com.hazelcast.config.DataConnectionConfigValidator;
import com.hazelcast.core.HazelcastException;
import com.hazelcast.dataconnection.DataConnection;
import com.hazelcast.dataconnection.DataConnectionRegistration;
import com.hazelcast.instance.impl.Node;
import com.hazelcast.internal.util.ServiceLoader;
import com.hazelcast.logging.ILogger;

import javax.annotation.Nonnull;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;

import static com.hazelcast.dataconnection.impl.DataConnectionServiceImpl.DataConnectionSource.CONFIG;
import static com.hazelcast.dataconnection.impl.DataConnectionServiceImpl.DataConnectionSource.SQL;
import static com.hazelcast.internal.util.ExceptionUtil.rethrow;

public class DataConnectionServiceImpl implements InternalDataConnectionService {

    private final Map> typeToDataConnectionClass = new HashMap<>();
    private final Map, String> dataConnectionClassToType = new HashMap<>();

    private final Map dataConnections = new ConcurrentHashMap<>();
    private final ILogger logger;

    public DataConnectionServiceImpl(Node node, ClassLoader classLoader) {
        this.logger = node.getLogger(getClass());
        processDataConnectionRegistrations(classLoader);

        for (DataConnectionConfig config : node.getConfig().getDataConnectionConfigs().values()) {
            put(config, CONFIG);
        }
    }

    private void processDataConnectionRegistrations(ClassLoader classLoader) {
        try {
            ServiceLoader.iterator(
                    DataConnectionRegistration.class,
                    DataConnectionRegistration.class.getName(),
                    classLoader
            ).forEachRemaining(registration -> {
                typeToDataConnectionClass.put(registration.type(), registration.clazz());
                dataConnectionClassToType.put(registration.clazz(), registration.type());
            });
        } catch (Exception e) {
            throw new HazelcastException("Could not register data connections", e);
        }
    }

    @Override
    public void createConfigDataConnection(DataConnectionConfig config) {
        put(config, CONFIG);
    }

    private void put(DataConnectionConfig config, DataConnectionSource source) {
        DataConnectionConfigValidator.validate(config);
        normalizeTypeName(config);
        dataConnections.compute(config.getName(),
                (key, current) -> {
                    if (current != null) {
                        if (current.source == CONFIG) {
                            throw new HazelcastException("Cannot replace a data connection created from configuration");
                        }
                        if (current.instance.getConfig().equals(config)) {
                            return current;
                        }
                        // close the old DataConnection
                        logger.fine("Asynchronously closing the old data connection: " + config.getName());
                        //noinspection resource
                        ForkJoinPool.commonPool().execute(() -> {
                            try {
                                current.instance.release();
                            } catch (Throwable e) {
                                logger.severe("Error when closing data connection '" + config.getName()
                                        + "', ignoring it: " + e, e);
                            }
                        });
                    }
                    return new DataConnectionEntry(createDataConnectionInstance(config), source);
                });
    }

    /**
     *  In handling types (e.g. type -> class mapping) we don't care about cases - we use lower cases.
     *  

* But we want to present Data Connections to the user in a consistent way, meaning that we should store "official" * name of the Data Connection instead of user-provided to use always the same style. */ private String normalizeTypeName(DataConnectionConfig config) { try { String type = normalizedTypeName(config.getType()); config.setType(type); return type; } catch (HazelcastException e) { throw new HazelcastException("Data connection '" + config.getName() + "' misconfigured: " + "unknown type '" + config.getType() + "'"); } } @Override public String normalizedTypeName(String type) { // exact match case if (typeToDataConnectionClass.containsKey(type)) { return type; } for (String possibleType : dataConnectionClassToType.values()) { if (type.equalsIgnoreCase(possibleType)) { return possibleType; } } throw new HazelcastException("Data connection type '" + type + "' is not known"); } @Override public void createOrReplaceSqlDataConnection(String name, String type, boolean shared, Map options) { put(toConfig(name, type, shared, options), SQL); } @Override public boolean existsConfigDataConnection(String name) { DataConnectionEntry dl = dataConnections.get(name); return dl != null && dl.source == CONFIG; } @Override public boolean existsSqlDataConnection(String name) { DataConnectionEntry dl = dataConnections.get(name); return dl != null && dl.source == SQL; } // package-private for testing purposes DataConnectionConfig toConfig(String name, String type, boolean shared, Map options) { Properties properties = PropertiesUtil.fromMap(options); return new DataConnectionConfig(name) .setType(type) .setShared(shared) .setProperties(properties); } private DataConnection createDataConnectionInstance(DataConnectionConfig config) { logger.finest("Creating '" + config.getName() + "' data connection"); String type = config.getType(); try { Class dataConnectionClass = typeToDataConnectionClass.get(normalizeTypeName(config)); Constructor constructor = dataConnectionClass.getConstructor(DataConnectionConfig.class); return constructor.newInstance(config); } catch (ClassCastException e) { throw new HazelcastException("Data connection '" + config.getName() + "' misconfigured: " + "'" + type + "' must implement '" + DataConnection.class.getName() + "'", e); } catch (Exception e) { throw rethrow(e); } } @Override public String typeForDataConnection(String name) { DataConnectionEntry dataConnection = dataConnections.get(name); if (dataConnection == null) { throw new HazelcastException("Data connection '" + name + "' not found"); } String type = dataConnectionClassToType.get(dataConnection.instance.getClass()); if (type == null) { throw new HazelcastException("Data connection type for class '" + dataConnection.getClass() + "' is not known"); } return type; } @Override public Class classForDataConnectionType(String type) { String caseInsensitiveType = normalizedTypeName(type); Class dataConnectionClass = typeToDataConnectionClass.get(caseInsensitiveType); if (dataConnectionClass == null) { throw new HazelcastException("Data connection type '" + type + "' is not known"); } return dataConnectionClass; } @Override @Nonnull public T getAndRetainDataConnection(String name, Class clazz) { DataConnectionEntry dataConnection = dataConnections.computeIfPresent(name, (k, v) -> { if (!clazz.isInstance(v.instance)) { throw new HazelcastException("Data connection '" + name + "' must be an instance of " + clazz); } v.instance.retain(); return v; }); if (dataConnection == null) { throw new HazelcastException("Data connection '" + name + "' not found"); } //noinspection unchecked return (T) dataConnection.instance; } @Override public void removeDataConnection(String name) { dataConnections.computeIfPresent(name, (k, v) -> { if (CONFIG.equals(v.source)) { throw new HazelcastException("Data connection '" + name + "' is configured via Config " + "and can't be removed"); } v.instance.release(); return null; }); } public List getConfigCreatedDataConnections() { return dataConnections.values() .stream() .filter(dl -> dl.source == CONFIG) .map(dl -> dl.instance) .collect(Collectors.toList()); } public List getSqlCreatedDataConnections() { return dataConnections.values() .stream() .filter(dl -> dl.source == SQL) .map(dl -> dl.instance) .collect(Collectors.toList()); } @Override public void shutdown() { for (Map.Entry entry : dataConnections.entrySet()) { logger.finest("Closing '" + entry.getKey() + "' data connection"); DataConnectionEntry dataConnection = entry.getValue(); try { dataConnection.instance.destroy(); } catch (Exception e) { logger.warning("Closing '" + entry.getKey() + "' data connection failed", e); } } } public enum DataConnectionSource { CONFIG, SQL } public static class DataConnectionEntry { private final DataConnection instance; private final DataConnectionSource source; DataConnectionEntry(DataConnection instance, DataConnectionSource source) { this.instance = instance; this.source = source; } public DataConnection getInstance() { return instance; } public DataConnectionSource getSource() { return source; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy