blob: 88289af387840aed9f139ba7b73adb770a990c7a [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.ambari.server.controller.jdbc;
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.*;
import org.apache.ambari.server.controller.utilities.PredicateHelper;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
/**
* Generic JDBC based resource provider.
*/
public class JDBCResourceProvider implements ResourceProvider {
private final Resource.Type type;
private final Set<String> propertyIds;
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<String, Map<String, String>>();
protected final static Logger LOG =
LoggerFactory.getLogger(JDBCResourceProvider.class);
public JDBCResourceProvider(ConnectionFactory connectionFactory,
Resource.Type type,
Set<String> propertyIds,
Map<Resource.Type, String> keyPropertyIds) {
this.connectionFactory = connectionFactory;
this.type = type;
this.propertyIds = propertyIds;
this.keyPropertyIds = keyPropertyIds;
}
@Override
public Set<Resource> getResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
Set<Resource> resources = new HashSet<Resource>();
Set<String> propertyIds = PropertyHelper.getRequestPropertyIds(this.propertyIds, 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 {
try {
Connection connection = connectionFactory.getConnection();
try {
Set<Map<String, Object>> propertySet = request.getProperties();
for (Map<String, Object> properties : propertySet) {
String sql = getInsertSQL(properties);
Statement statement = connection.createStatement();
statement.execute(sql);
statement.close();
}
} finally {
connection.close();
}
} catch (SQLException e) {
throw new IllegalStateException("DB error : ", e);
}
return getRequestStatus();
}
@Override
public RequestStatus updateResources(Request request, Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
try {
Connection connection = connectionFactory.getConnection();
try {
Set<Map<String, Object>> propertySet = request.getProperties();
Map<String, Object> properties = propertySet.iterator().next();
String sql = getUpdateSQL(properties, predicate);
Statement statement = connection.createStatement();
statement.execute(sql);
statement.close();
} finally {
connection.close();
}
} catch (SQLException e) {
throw new IllegalStateException("DB error : ", e);
}
return getRequestStatus();
}
@Override
public RequestStatus deleteResources(Predicate predicate)
throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
try {
Connection connection = connectionFactory.getConnection();
try {
String sql = getDeleteSQL(predicate);
Statement statement = connection.createStatement();
statement.execute(sql);
statement.close();
} finally {
connection.close();
}
} catch (SQLException e) {
throw new IllegalStateException("DB error : ", e);
}
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<String>();
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 Set<String> getPropertyIdsForSchema() {
return propertyIds;
}
@Override
public Map<Resource.Type, String> getKeyPropertyIds() {
return keyPropertyIds;
}
@Override
public Set<String> checkPropertyIds(Set<String> propertyIds) {
if (!this.propertyIds.containsAll(propertyIds)) {
Set<String> unsupportedPropertyIds = new HashSet<String>(propertyIds);
unsupportedPropertyIds.removeAll(this.propertyIds);
return unsupportedPropertyIds;
}
return Collections.emptySet();
}
/**
* 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<String, String>();
this.importedKeys.put(table, importedKeys);
DatabaseMetaData metaData = connection.getMetaData();
ResultSet 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);
}
}
}
/**
* 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<String>();
for (String propertyId : propertyIds) {
tables.add(PropertyHelper.getPropertyCategory(propertyId));
}
return tables;
}
}