/** * 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.ambari.server.controller.jdbc; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.ambari.server.controller.internal.BaseProvider; import org.apache.ambari.server.controller.internal.RequestStatusImpl; import org.apache.ambari.server.controller.internal.ResourceImpl; import org.apache.ambari.server.controller.predicate.BasePredicate; import org.apache.ambari.server.controller.predicate.PredicateVisitorAcceptor; import org.apache.ambari.server.controller.spi.NoSuchParentResourceException; import org.apache.ambari.server.controller.spi.NoSuchResourceException; import org.apache.ambari.server.controller.spi.Predicate; import org.apache.ambari.server.controller.spi.Request; import org.apache.ambari.server.controller.spi.RequestStatus; import org.apache.ambari.server.controller.spi.Resource; import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException; import org.apache.ambari.server.controller.spi.ResourceProvider; import org.apache.ambari.server.controller.spi.SystemException; import org.apache.ambari.server.controller.spi.UnsupportedPropertyException; import org.apache.ambari.server.controller.utilities.PredicateHelper; import org.apache.ambari.server.controller.utilities.PropertyHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Generic JDBC based resource provider. * TODO : Not used. Move to Test for API integration testing. */ public class JDBCResourceProvider extends BaseProvider implements ResourceProvider { private final Resource.Type type; private final ConnectionFactory connectionFactory; /** * The schema for this provider's resource type. */ private final Map<Resource.Type, String> keyPropertyIds; /** * Key mappings used for joins. */ private final Map<String, Map<String, String>> importedKeys = new HashMap<>(); protected final static Logger LOG = LoggerFactory.getLogger(JDBCResourceProvider.class); public JDBCResourceProvider(ConnectionFactory connectionFactory, Resource.Type type, Set<String> propertyIds, Map<Resource.Type, String> keyPropertyIds) { super(propertyIds); this.connectionFactory = connectionFactory; this.type = type; this.keyPropertyIds = keyPropertyIds; } @Override public Set<Resource> getResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Set<Resource> resources = new HashSet<>(); Set<String> propertyIds = getRequestPropertyIds(request, predicate); // Can't allow these properties with the old schema... propertyIds.remove(PropertyHelper.getPropertyId("Clusters", "cluster_id")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "disk_info")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "public_host_name")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "last_registration_time")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "host_state")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "last_heartbeat_time")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "host_health_report")); propertyIds.remove(PropertyHelper.getPropertyId("Hosts", "host_status")); propertyIds.remove(PropertyHelper.getPropertyId("ServiceInfo", "desired_configs")); propertyIds.remove(PropertyHelper.getPropertyId("ServiceComponentInfo", "desired_configs")); propertyIds.remove(PropertyHelper.getPropertyId("HostRoles", "configs")); propertyIds.remove(PropertyHelper.getPropertyId("HostRoles", "desired_configs")); Connection connection = null; Statement statement = null; ResultSet rs = null; try { connection = connectionFactory.getConnection(); for (String table : getTables(propertyIds)) { getImportedKeys(connection, table); } String sql = getSelectSQL(propertyIds, predicate); statement = connection.createStatement(); rs = statement.executeQuery(sql); while (rs.next()) { ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); final ResourceImpl resource = new ResourceImpl(type); for (int i = 1; i <= columnCount; ++i) { String propertyId = PropertyHelper.getPropertyId(metaData.getTableName(i), metaData.getColumnName(i)); if (propertyIds.contains(propertyId)) { resource.setProperty(propertyId, rs.getString(i)); } } resources.add(resource); } statement.close(); } catch (SQLException e) { if (LOG.isDebugEnabled()) { LOG.debug("Caught exception getting resource.", e); } return Collections.emptySet(); } finally { try { if (rs != null) rs.close(); } catch (SQLException e) { LOG.error("Exception while closing ResultSet", e); } try { if (statement != null) statement.close(); } catch (SQLException e) { LOG.error("Exception while closing statment", e); } try { if (connection != null) connection.close(); } catch (SQLException e) { LOG.error("Exception while closing statment", e); } } return resources; } @Override public RequestStatus createResources(Request request) throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException { Connection connection = null; try { connection = connectionFactory.getConnection(); Statement statement = null; try { Set<Map<String, Object>> propertySet = request.getProperties(); statement = connection.createStatement(); for (Map<String, Object> properties : propertySet) { String sql = getInsertSQL(properties); statement.execute(sql); } } finally { if (statement != null) { statement.close(); } } } catch (SQLException e) { throw new IllegalStateException("DB error : ", e); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { throw new IllegalStateException("DB error : ", ex); } } } return getRequestStatus(); } @Override public RequestStatus updateResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Connection connection = null; try { connection = connectionFactory.getConnection(); Statement statement = null; try { Set<Map<String, Object>> propertySet = request.getProperties(); Map<String, Object> properties = propertySet.iterator().next(); String sql = getUpdateSQL(properties, predicate); statement = connection.createStatement(); statement.execute(sql); } finally { if (statement != null) { statement.close(); } } } catch (SQLException e) { throw new IllegalStateException("DB error : ", e); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { throw new IllegalStateException("DB error : ", ex); } } } return getRequestStatus(); } @Override public RequestStatus deleteResources(Request request, Predicate predicate) throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException { Connection connection = null; try { connection = connectionFactory.getConnection(); Statement statement = null; try { String sql = getDeleteSQL(predicate); statement = connection.createStatement(); statement.execute(sql); } finally { if (statement != null) { statement.close(); } } } catch (SQLException e) { throw new IllegalStateException("DB error : ", e); } finally { if (connection != null) { try { connection.close(); } catch (SQLException ex) { throw new IllegalStateException("DB error : ", ex); } } } return getRequestStatus(); } private String getInsertSQL(Map<String, Object> properties) { StringBuilder columns = new StringBuilder(); StringBuilder values = new StringBuilder(); String table = null; for (Map.Entry<String, Object> entry : properties.entrySet()) { String propertyId = entry.getKey(); Object propertyValue = entry.getValue(); table = PropertyHelper.getPropertyCategory(propertyId); if (columns.length() > 0) { columns.append(", "); } columns.append(PropertyHelper.getPropertyName(propertyId)); if (values.length() > 0) { values.append(", "); } values.append("'"); values.append(propertyValue); values.append("'"); } return "insert into " + table + " (" + columns + ") values (" + values + ")"; } private String getSelectSQL(Set<String> propertyIds, Predicate predicate) { StringBuilder columns = new StringBuilder(); Set<String> tableSet = new HashSet<>(); for (String propertyId : propertyIds) { if (columns.length() > 0) { columns.append(", "); } String propertyCategory = PropertyHelper.getPropertyCategory(propertyId); columns.append(propertyCategory).append(".").append(PropertyHelper.getPropertyName(propertyId)); tableSet.add(propertyCategory); } boolean haveWhereClause = false; StringBuilder whereClause = new StringBuilder(); if (predicate != null && propertyIds.containsAll(PredicateHelper.getPropertyIds(predicate)) && predicate instanceof PredicateVisitorAcceptor) { SQLPredicateVisitor visitor = new SQLPredicateVisitor(); ((PredicateVisitorAcceptor) predicate).accept(visitor); whereClause.append(visitor.getSQL()); haveWhereClause = true; } StringBuilder joinClause = new StringBuilder(); if (tableSet.size() > 1) { for (String table : tableSet) { Map<String, String> joinKeys = importedKeys.get(table); if (joinKeys != null) { for (Map.Entry<String, String> entry : joinKeys.entrySet()) { String category1 = PropertyHelper.getPropertyCategory(entry.getKey()); String category2 = PropertyHelper.getPropertyCategory(entry.getValue()); if (tableSet.contains(category1) && tableSet.contains(category2)) { if (haveWhereClause) { joinClause.append(" AND "); } joinClause.append(category1).append(".").append(PropertyHelper.getPropertyName(entry.getKey())); joinClause.append(" = "); joinClause.append(category2).append(".").append(PropertyHelper.getPropertyName(entry.getValue())); tableSet.add(category1); tableSet.add(category2); haveWhereClause = true; } } } } } StringBuilder tables = new StringBuilder(); for (String table : tableSet) { if (tables.length() > 0) { tables.append(", "); } tables.append(table); } String sql = "select " + columns + " from " + tables; if (haveWhereClause) { sql = sql + " where " + whereClause + joinClause; } return sql; } private String getDeleteSQL(Predicate predicate) { StringBuilder whereClause = new StringBuilder(); if (predicate instanceof BasePredicate) { BasePredicate basePredicate = (BasePredicate) predicate; SQLPredicateVisitor visitor = new SQLPredicateVisitor(); basePredicate.accept(visitor); whereClause.append(visitor.getSQL()); String table = PropertyHelper.getPropertyCategory(basePredicate.getPropertyIds().iterator().next()); return "delete from " + table + " where " + whereClause; } throw new IllegalStateException("Can't generate SQL."); } private String getUpdateSQL(Map<String, Object> properties, Predicate predicate) { if (predicate instanceof BasePredicate) { StringBuilder whereClause = new StringBuilder(); BasePredicate basePredicate = (BasePredicate) predicate; SQLPredicateVisitor visitor = new SQLPredicateVisitor(); basePredicate.accept(visitor); whereClause.append(visitor.getSQL()); String table = PropertyHelper.getPropertyCategory(basePredicate.getPropertyIds().iterator().next()); StringBuilder setClause = new StringBuilder(); for (Map.Entry<String, Object> entry : properties.entrySet()) { if (setClause.length() > 0) { setClause.append(", "); } setClause.append(PropertyHelper.getPropertyName(entry.getKey())); setClause.append(" = "); setClause.append("'"); setClause.append(entry.getValue()); setClause.append("'"); } return "update " + table + " set " + setClause + " where " + whereClause; } throw new IllegalStateException("Can't generate SQL."); } @Override public Map<Resource.Type, String> getKeyPropertyIds() { return keyPropertyIds; } /** * Lazily populate the imported key mappings for the given table. * * @param connection the connection to use to obtain the database meta data * @param table the table * @throws SQLException thrown if the meta data for the given connection cannot be obtained */ private void getImportedKeys(Connection connection, String table) throws SQLException { if (!this.importedKeys.containsKey(table)) { Map<String, String> importedKeys = new HashMap<>(); this.importedKeys.put(table, importedKeys); DatabaseMetaData metaData = connection.getMetaData(); ResultSet rs = null; try { rs = metaData.getImportedKeys(connection.getCatalog(), null, table); while (rs.next()) { String pkPropertyId = PropertyHelper.getPropertyId( rs.getString("PKTABLE_NAME"), rs.getString("PKCOLUMN_NAME")); String fkPropertyId = PropertyHelper.getPropertyId( rs.getString("FKTABLE_NAME"), rs.getString("FKCOLUMN_NAME")); importedKeys.put(pkPropertyId, fkPropertyId); } } finally { if (rs != null) { rs.close(); } } } } /** * Get a request status * * @return the request status */ private RequestStatus getRequestStatus() { return new RequestStatusImpl(null); } /** * Get the set of tables associated with the given property ids. * * @param propertyIds the property ids * @return the set of tables */ private static Set<String> getTables(Set<String> propertyIds) { Set<String> tables = new HashSet<>(); for (String propertyId : propertyIds) { tables.add(PropertyHelper.getPropertyCategory(propertyId)); } return tables; } }