blob: cd28c72a3877054e6c351ad4f9070e291eb3f8ba [file] [log] [blame]
/*
* 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.geode.connectors.jdbc.internal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
import org.apache.logging.log4j.Logger;
import org.apache.geode.annotations.Experimental;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.Region;
import org.apache.geode.connectors.jdbc.JdbcConnectorException;
import org.apache.geode.connectors.jdbc.JdbcWriter;
import org.apache.geode.connectors.jdbc.internal.configuration.FieldMapping;
import org.apache.geode.connectors.jdbc.internal.configuration.RegionMapping;
import org.apache.geode.internal.cache.CacheService;
import org.apache.geode.internal.jndi.JNDIInvoker;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.management.internal.beans.CacheServiceMBeanBase;
@Experimental
public class JdbcConnectorServiceImpl implements JdbcConnectorService {
private static final Logger logger = LogService.getLogger();
private final Map<String, RegionMapping> mappingsByRegion =
new ConcurrentHashMap<>();
@Override
public Set<RegionMapping> getRegionMappings() {
Set<RegionMapping> regionMappings = new HashSet<>();
regionMappings.addAll(mappingsByRegion.values());
return regionMappings;
}
@Override
public void createRegionMapping(RegionMapping mapping)
throws RegionMappingExistsException {
RegionMapping existing =
mappingsByRegion.putIfAbsent(mapping.getRegionName(), mapping);
if (existing != null) {
throw new RegionMappingExistsException(
"JDBC mapping for region " + mapping.getRegionName() + " exists");
}
}
@Override
public void replaceRegionMapping(RegionMapping alteredMapping)
throws RegionMappingNotFoundException {
RegionMapping existingMapping =
mappingsByRegion.get(alteredMapping.getRegionName());
if (existingMapping == null) {
throw new RegionMappingNotFoundException(
"JDBC mapping for the region " + alteredMapping.getRegionName() + " was not found");
}
mappingsByRegion.put(existingMapping.getRegionName(), alteredMapping);
}
@Override
public boolean isMappingSynchronous(String regionName, Cache cache) {
Region<?, ?> region = cache.getRegion(regionName);
if (region == null) {
throw new IllegalStateException("Region for mapping could not be found.");
}
// If our region has a Jdbc Writer set as the cache writer then we know it is syncronous
return region.getAttributes().getCacheWriter() != null
&& region.getAttributes().getCacheWriter() instanceof JdbcWriter;
}
@Override
public void validateMapping(RegionMapping regionMapping) {
DataSource dataSource = getDataSource(regionMapping.getDataSourceName());
if (dataSource == null) {
throw new JdbcConnectorException("No datasource \"" + regionMapping.getDataSourceName()
+ "\" found when creating mapping \"" + regionMapping.getRegionName() + "\"");
}
validateMapping(regionMapping, dataSource);
}
@Override
public void validateMapping(RegionMapping regionMapping, DataSource dataSource) {
TableMetaDataView metaDataView = getTableMetaDataView(regionMapping, dataSource);
boolean foundDifference = false;
if (regionMapping.getFieldMappings().size() != metaDataView.getColumnNames().size()) {
foundDifference = true;
} else {
for (FieldMapping fieldMapping : regionMapping.getFieldMappings()) {
String jdbcName = fieldMapping.getJdbcName();
if (!metaDataView.getColumnNames().contains(jdbcName)) {
foundDifference = true;
break;
}
if (!metaDataView.getColumnDataType(jdbcName).getName()
.equals(fieldMapping.getJdbcType())) {
foundDifference = true;
break;
}
if (metaDataView.isColumnNullable(jdbcName) != fieldMapping.isJdbcNullable()) {
foundDifference = true;
break;
}
}
}
if (!foundDifference) {
if (!regionMapping.getSpecifiedIds()
&& !regionMapping.getIds().equals(String.join(",", metaDataView.getKeyColumnNames()))) {
foundDifference = true;
}
}
if (foundDifference) {
StringBuilder sb = new StringBuilder();
sb.append(
"Error detected when comparing mapping for region \"" + regionMapping.getRegionName()
+ "\" with table definition: \n");
if (!regionMapping.getSpecifiedIds()) {
sb.append("\nId fields in Field Mappings: " + regionMapping.getIds());
sb.append(
"\nId fields in Table MetaData: " + String.join(",", metaDataView.getKeyColumnNames()));
}
sb.append("\n\nDefinition from Field Mappings (" + regionMapping.getFieldMappings().size()
+ " field mappings found):");
for (FieldMapping fieldMapping : regionMapping.getFieldMappings()) {
sb.append("\n" + fieldMapping.getJdbcName() + " - " + fieldMapping.getJdbcType());
}
sb.append("\n\nDefinition from Table Metadata (" + metaDataView.getColumnNames().size()
+ " columns found):");
for (String name : metaDataView.getColumnNames()) {
sb.append("\n" + name + " - " + metaDataView.getColumnDataType(name));
}
sb.append("\n\nDestroy and recreate the JDBC mapping for \"" + regionMapping.getRegionName()
+ "\" to resolve this error.");
logger.error(sb.toString());
throw new JdbcConnectorException("Jdbc mapping for \"" + regionMapping.getRegionName()
+ "\" does not match table definition, check logs for more details.");
}
}
@Override
public RegionMapping getMappingForRegion(String regionName) {
return mappingsByRegion.get(regionName);
}
@Override
public void destroyRegionMapping(String regionName) {
mappingsByRegion.remove(regionName);
}
@Override
public Class<? extends CacheService> getInterface() {
return JdbcConnectorService.class;
}
@Override
public CacheServiceMBeanBase getMBean() {
return null;
}
// The following helper method is to allow for proper mocking in unit tests
DataSource getDataSource(String dataSourceName) {
return JNDIInvoker.getDataSource(dataSourceName);
}
// The following helper method is to allow for proper mocking in unit tests
TableMetaDataManager getTableMetaDataManager() {
return new TableMetaDataManager();
}
private TableMetaDataView getTableMetaDataView(RegionMapping regionMapping,
DataSource dataSource) {
TableMetaDataManager manager = getTableMetaDataManager();
try (Connection connection = dataSource.getConnection()) {
return manager.getTableMetaDataView(connection, regionMapping);
} catch (SQLException ex) {
throw JdbcConnectorException
.createException("Exception thrown while connecting to datasource \""
+ regionMapping.getDataSourceName() + "\": ", ex);
}
}
}