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

com.att.research.xacml.std.pip.engines.jdbc.ConfigurableJDBCResolver Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/*
 *
 *          Copyright (c) 2013,2019  AT&T Knowledge Ventures
 *                     SPDX-License-Identifier: MIT
 */

package com.att.research.xacml.std.pip.engines.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.att.research.xacml.api.Attribute;
import com.att.research.xacml.api.AttributeValue;
import com.att.research.xacml.api.DataType;
import com.att.research.xacml.api.DataTypeFactory;
import com.att.research.xacml.api.Identifier;
import com.att.research.xacml.api.XACML3;
import com.att.research.xacml.api.pip.PIPEngine;
import com.att.research.xacml.api.pip.PIPException;
import com.att.research.xacml.api.pip.PIPFinder;
import com.att.research.xacml.api.pip.PIPRequest;
import com.att.research.xacml.api.pip.PIPResponse;
import com.att.research.xacml.std.StdAttribute;
import com.att.research.xacml.std.datatypes.DataTypes;
import com.att.research.xacml.std.datatypes.ISO8601Date;
import com.att.research.xacml.std.datatypes.ISO8601DateTime;
import com.att.research.xacml.std.pip.StdPIPRequest;
import com.att.research.xacml.std.pip.engines.Configurables;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;

/**
 * Implements the {@link com.att.research.xacml.std.pip.engines.jdbc.JDBCResolver} for SQL queries with parameters in
 * their prepared statements specified as XACML Attribute values.
 * 
 * @author Christopher A. Rath
 * @version $Revision$
 */
public class ConfigurableJDBCResolver implements JDBCResolver {
	public static final String PROP_SELECT			= "select";
	public static final String PROP_SELECT_FIELDS	= "fields";
	public static final String PROP_SELECT_FIELD	= "field";
	public static final String PROP_SELECT_PARAMETERS	= "parameters";
	public static final String PROP_SELECT_PARAMETER	= "parameter";
	
	private Logger logger								= LoggerFactory.getLogger(this.getClass());
	private String defaultIssuer;
	private Set supportedRequests			= new HashSet();
	private Set supportedRequestsNoIssuer	= new HashSet();
	private Map mapFields			= new HashMap();
	private String sqlQuery;
	private List parameters					= new ArrayList();
	private static DataTypeFactory dataTypeFactory		= null;
	
	static {
		try {
			dataTypeFactory	= DataTypeFactory.newInstance();
		} catch (Exception ex) {
			LoggerFactory.getLogger(ConfigurableJDBCResolver.class).error("Exception geting DataTypeFactory: " + ex.toString(), ex);
		}
	}
	
	/**
	 * Determines if the given {@link com.att.research.xacml.api.pip.PIPRequest} can be answered with this ConfigurableJDBCResolver.
	 * 
	 * @param pipRequest the PIPRequest to check
	 * @return true if the given PIPRequest is supported by this ConfigurableJDBCResolver, else false
	 */
	protected boolean isSupported(PIPRequest pipRequest) {
		if (pipRequest.getIssuer() == null) {
			return this.supportedRequestsNoIssuer.contains(pipRequest);
		} else {
			return this.supportedRequests.contains(pipRequest);
		}
	}
	
	/**
	 * Creates a new ConfigurableJDBCResolver that can provide XACML Attributes for the given Collection of
	 * {@link com.att.research.xacml.api.pip.PIPRequests}s.  The mapping from database table field names to XACML Attributes is provided by the
	 * fieldsIn Map.  The SQL query String is provided by sqlQueryIn.  The query string may contain
	 * prepared statement parameter place-holders ('?').  The XACML Attributes whose values are used for those place-holders are provided 
	 * by the given parametersIn List.
	 * 
	 * @param supportedRequestsIn the Collection of PIPRequests that are supported by the new ConfiurableJDBCResolver
	 * @param fieldsIn the Map from String field names to PIPRequests in the database table
	 * @param sqlQueryIn the String SQL query that retrieves records that satisfy PIPRequests
	 * @param parametersIn the List of PIPRequests representing parameter values found in sqlQueryIn.
	 */
	/*
	public ConfigurableJDBCResolver(Collection supportedRequestsIn, Map fieldsIn, String sqlQueryIn, List parametersIn) {
		this();
		if (supportedRequestsIn != null) {
			this.supportedRequests.addAll(supportedRequestsIn);
			for (PIPRequest pipRequest : supportedRequestsIn) {
				if (pipRequest.getIssuer() != null) {
					this.supportedRequestsNoIssuer.add(new StdPIPRequest(pipRequest.getCategory(), pipRequest.getAttributeId(), pipRequest.getDataTypeId()));
				} else {
					this.supportedRequestsNoIssuer.add(pipRequest);
				}
			}
		}
		if (fieldsIn != null) {
			for (String field : fieldsIn.keySet()) {
				this.mapFields.put(field, fieldsIn.get(field));
			}
		}
		this.sqlQuery	= sqlQueryIn;
		if (parametersIn != null) {
			this.parameters.addAll(parametersIn);
		}
	}
	*/
	
	public ConfigurableJDBCResolver() {
		if (dataTypeFactory == null) {
			throw new IllegalStateException("No DataTypeFactory instance created");
		}		
	}
	
	public Map getMapFields() {
		return mapFields;
	}

	public List getParameters() {
		return parameters;
	}
	
	public Properties	generateProperties(String id, String select) {
		return generateProperties(id, select, this.mapFields, this.parameters);
	}
	
	public static Properties	generateProperties(String id, String select, Map mapFields, List parameters) {
		Properties properties = new Properties();
		//
		// Set the select statement
		//
		properties.setProperty(Joiner.on('.').join(id, PROP_SELECT), select);
		//
		// Set the fields
		//
		if (mapFields.size() > 0) {
			properties.setProperty(Joiner.on('.').join(id, PROP_SELECT_FIELDS), Joiner.on(',').join(mapFields.keySet()));
			for (String field : mapFields.keySet()) {
				PIPRequest request = mapFields.get(field);
				String fieldPrefix = Joiner.on('.').join(id, PROP_SELECT_FIELD);
				properties.setProperty(fieldPrefix + ".id", request.getAttributeId().stringValue());
				properties.setProperty(fieldPrefix + ".datatype", request.getDataTypeId().stringValue());
				properties.setProperty(fieldPrefix + ".category", request.getCategory().stringValue());
				if (request.getIssuer() != null) {
					properties.setProperty(fieldPrefix + ".issuer", request.getIssuer());
				}
			}
		}
		//
		// Set the parameters
		//
		if (parameters.size() > 0) {
			String params = "1";
			for (int i = 2; i <= parameters.size(); i++) {
				params = params + "," + i; 
			}
			properties.setProperty(Joiner.on('.').join(id, PROP_SELECT_PARAMETERS), params);
			int position = 1;
			for (PIPRequest request : parameters) {
				String fieldPrefix = Joiner.on('.').join(id, PROP_SELECT_PARAMETER, position++);
				properties.setProperty(fieldPrefix + ".id", request.getAttributeId().stringValue());
				properties.setProperty(fieldPrefix + ".datatype", request.getDataTypeId().stringValue());
				properties.setProperty(fieldPrefix + ".category", request.getCategory().stringValue());
				if (request.getIssuer() != null) {
					properties.setProperty(fieldPrefix + ".issuer", request.getIssuer());
				}
			}
		}
		return properties;
	}

	/*
	protected PIPRequest getPIPRequest(String idPrefix, Properties properties) throws PIPException {
		String stringProp	= idPrefix + PROP_ID;
		String attributeId	= properties.getProperty(stringProp);
		if (attributeId == null || attributeId.length() == 0) {
			this.logger.error("No '" + stringProp + "' property");
			throw new PIPException("No '" + stringProp + "' property");
		}
		
		stringProp			= idPrefix + PROP_DATATYPE;
		String dataTypeId	= properties.getProperty(stringProp);
		if (dataTypeId == null || dataTypeId.length() == 0) {
			this.logger.error("No '" + stringProp + "' property");
			throw new PIPException("No '" + stringProp + "' property");			
		}
		
		stringProp			= idPrefix + PROP_CATEGORY;
		String categoryId	= properties.getProperty(stringProp);
		if (categoryId == null) {
			this.logger.error("No '" + stringProp + "' property");
			throw new PIPException("No '" + stringProp + "' property");						
		}
		
		stringProp			= idPrefix + PROP_ISSUER;
		String issuer		= properties.getProperty(stringProp);
		
		return new StdPIPRequest(new IdentifierImpl(categoryId), new IdentifierImpl(attributeId), new IdentifierImpl(dataTypeId), issuer);
	}
*/

	protected void configureField(String id, String fieldName, Properties properties) throws PIPException {
		PIPRequest pipRequestField	= Configurables.getPIPRequest(id + "." + PROP_SELECT_FIELD + "." + fieldName, properties, this.defaultIssuer);
		this.supportedRequests.add(pipRequestField);
		this.supportedRequestsNoIssuer.add(new StdPIPRequest(pipRequestField.getCategory(), pipRequestField.getAttributeId(), pipRequestField.getDataTypeId()));
		this.mapFields.put(fieldName, pipRequestField);
	}
	
	protected void configureParameter(String id, String parameterName, Properties properties) throws PIPException {
		PIPRequest pipRequestParameter	= Configurables.getPIPRequest(id + "." + PROP_SELECT_PARAMETER + "." + parameterName, properties, null);
		this.parameters.add(pipRequestParameter);
	}

	@Override
	public void configure(String id, Properties properties, String defaultIssuer) throws PIPException {
		/*
		 * Save our default issuer
		 */
		this.defaultIssuer = defaultIssuer;
		/*
		 * Get the SELECT statement to be used in the prepared statement
		 */
		String idPrefix		= id + ".";
		String stringProp	= idPrefix + PROP_SELECT;
		this.sqlQuery		= properties.getProperty(stringProp);
		if (this.sqlQuery == null || this.sqlQuery.length() == 0) {
			this.logger.error("No '" + stringProp + "' property");
			throw new PIPException("No '" + stringProp + "' property");
		}
		
		/*
		 * Get the list of database columns returned by the query
		 */
		stringProp			= idPrefix + PROP_SELECT_FIELDS;
		String fields		= properties.getProperty(stringProp);
		if (fields == null || fields.length() == 0) {
			this.logger.error("No '" + stringProp + "' property");
			throw new PIPException("No '" + stringProp + "' property");
		}
		for (String field : Splitter.on(',').trimResults().omitEmptyStrings().split(fields)) {
			this.configureField(id, field, properties);
		}
		
		/*
		 * Get the list of query parameters.  This may be null
		 */
		stringProp			= idPrefix + PROP_SELECT_PARAMETERS;
		String parameters	= properties.getProperty(stringProp);
		if (parameters != null && parameters.length() > 0) {
			for (String parameter : Splitter.on(',').trimResults().omitEmptyStrings().split(parameters)) {
				this.configureParameter(id, parameter, properties);
			}
		}
	}

	@Override
	public PreparedStatement getPreparedStatement(PIPEngine pipEngine, PIPRequest pipRequest, PIPFinder pipFinder, Connection connection) throws PIPException {
		/*
		 * Do we support the request?
		 */
		if (!this.isSupported(pipRequest)) {
			return null;
		}
		
		PreparedStatement preparedStatement	= null;
		try {
			preparedStatement	= connection.prepareStatement(this.sqlQuery);
		} catch (SQLException ex) {
			this.logger.error("SQLException creating PreparedStatement: " + ex.toString(), ex);
			// TODO: throw the exception or return a null PreparedStatement?
			return null;
		}

		if (this.parameters.size() > 0) {
			/*
			 * Gather all of the AttributeValues for parameters to the prepared statement.  For now, we assume a single value for each
			 * parameter.  If there are multiple values we will log an error and return a null PreparedStatement.
			 * 
			 * TODO: Should the interface change to return a cross-product of PreparedStatements to deal with multiple values for parameters?
			 * If not, should we just take the first value and use it as the parameter value?
			 */
			for (int i = 0 ; i < this.parameters.size() ; i++) {
				PIPRequest pipRequestParameter	= this.parameters.get(i);
				PIPResponse pipResponse	= pipFinder.getMatchingAttributes(pipRequestParameter, null);
				if (pipResponse.getStatus() == null || pipResponse.getStatus().isOk()) {
					Collection listAttributes	= pipResponse.getAttributes();
					if (listAttributes.size() > 0) {
						if (listAttributes.size() > 1) {
							this.logger.error("PIPFinder returned more than one Attribute for " + pipRequestParameter.toString());
							throw new PIPException("PIPFinder returned more than one Attribute for " + pipRequestParameter.toString());
						}
						Collection> listAttributeValuesReturned	= listAttributes.iterator().next().getValues();
						if (listAttributeValuesReturned.size() > 0) {
							if (listAttributeValuesReturned.size() > 1) {
								this.logger.warn("PIPFinder returned more than one AttributeValue for " + pipRequestParameter.toString());
								return null;
							}
							AttributeValue attributeValue			= listAttributeValuesReturned.iterator().next();
							Identifier identifierAttributeValueDataType	= attributeValue.getDataTypeId();
							try {
								if (identifierAttributeValueDataType.equals(XACML3.ID_DATATYPE_INTEGER)) {
									preparedStatement.setInt(i+1, DataTypes.DT_INTEGER.convert(attributeValue.getValue()).intValue());
								} else if (identifierAttributeValueDataType.equals(XACML3.ID_DATATYPE_DOUBLE)) {
									preparedStatement.setDouble(i+1, DataTypes.DT_DOUBLE.convert(attributeValue.getValue()));
								} else if (identifierAttributeValueDataType.equals(XACML3.ID_DATATYPE_BOOLEAN)) {
									preparedStatement.setBoolean(i+1, DataTypes.DT_BOOLEAN.convert(attributeValue.getValue()));
								} else if (identifierAttributeValueDataType.equals(XACML3.ID_DATATYPE_DATETIME)) {
									ISO8601DateTime iso8601DateTime	= DataTypes.DT_DATETIME.convert(attributeValue.getValue());
									java.sql.Date sqlDate			= new java.sql.Date(iso8601DateTime.getCalendar().getTimeInMillis());
									preparedStatement.setDate(i+1, sqlDate, iso8601DateTime.getCalendar());
								} else if (identifierAttributeValueDataType.equals(XACML3.ID_DATATYPE_DATE)) {
									ISO8601Date iso8601Date	= DataTypes.DT_DATE.convert(attributeValue.getValue());
									java.sql.Date sqlDate			= new java.sql.Date(iso8601Date.getCalendar().getTimeInMillis());
									preparedStatement.setDate(i+1, sqlDate, iso8601Date.getCalendar());
								} else {
									preparedStatement.setString(i+1, DataTypes.DT_STRING.convert(attributeValue.getValue()));
								}
							} catch (Exception ex) {
								this.logger.error("Exception setting parameter " + (i+1) + " to " + attributeValue.toString() + ": " + ex.toString(), ex);
								return null;
							}
						} else {
							this.logger.warn("No AttributeValues returned for parameter " + pipRequestParameter.toString());
							return null;
						}
					} else {
						this.logger.warn("No Attributes returned for parameter " + pipRequestParameter.toString());
						return null;
					}
				} else {
					this.logger.warn("PIPFinder returned status " + pipResponse.getStatus().toString());
					return null;
				}
			}
		}
		
		return preparedStatement;
	}
	
	/**
	 * Creates an {@link com.att.research.xacml.api.Attribute} from the value associated with the field with the given fieldName.
	 * 
	 * @param resultSet the {@link java.sql.ResultSet} containing the current row from the database
	 * @param fieldName the String name of the field containing the attribute value
	 * @param pipRequestAttribute the {@link com.att.research.xacml.api.pip.PIPRequest} for the Attribute to create
	 * @return a new Attribute with the value of the given fieldName.
	 */
	protected Attribute getAttributeFromResultSet(ResultSet resultSet, String fieldName, PIPRequest pipRequestAttribute) {
		AttributeValue attributeValue	= null;
		Identifier identifierDataType		= pipRequestAttribute.getDataTypeId();
		try {
			DataType dataType				= dataTypeFactory.getDataType(identifierDataType);
			if (dataType == null) {
				this.logger.warn("Unknown data type " + pipRequestAttribute.getDataTypeId().stringValue());
				return null;
			}
			/*
			 * Try to find the column index
			 */
			
			int columnIndex = -1;
			
			try {
				columnIndex = resultSet.findColumn(fieldName);
			} catch (Exception e) {
				/*
				 * The field name could be an integer, let's try that
				 */
				try {
					columnIndex = Integer.parseInt(fieldName);
				} catch (Exception e1) {
					logger.error("Failed to find column with label " + fieldName);
				}
			}
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Column " + fieldName + " maps to column index " + columnIndex);
			}
			
			/*
			 * Catch special cases for database types
			 */
			if (identifierDataType.equals(XACML3.ID_DATATYPE_BOOLEAN)) {
				attributeValue	= dataType.createAttributeValue(resultSet.getBoolean(columnIndex));
			} else if (identifierDataType.equals(XACML3.ID_DATATYPE_DATE) || identifierDataType.equals(XACML3.ID_DATATYPE_DATETIME)) {
				attributeValue	= dataType.createAttributeValue(resultSet.getDate(columnIndex));
			} else if (identifierDataType.equals(XACML3.ID_DATATYPE_DOUBLE)) {
				attributeValue	= dataType.createAttributeValue(resultSet.getDouble(columnIndex));
			} else if (identifierDataType.equals(XACML3.ID_DATATYPE_INTEGER)) {
				attributeValue	= dataType.createAttributeValue(resultSet.getInt(columnIndex));
			} else {
				/*
				 * Default to convert the string value from the database to the requested data type
				 */
				String stringValue	= resultSet.getString(columnIndex);
				if (stringValue != null) {
					attributeValue	= dataType.createAttributeValue(stringValue);
				}
			}
		} catch (Exception ex) {
			this.logger.error("Exception getting value for fieldName '" + fieldName + "' as a " + identifierDataType.stringValue() + ": " + ex.toString(), ex);
			return null;
		}
		String issuer = this.defaultIssuer;
		if (pipRequestAttribute.getIssuer() != null) {
			issuer = pipRequestAttribute.getIssuer();
		}
		return new StdAttribute(pipRequestAttribute.getCategory(), pipRequestAttribute.getAttributeId(), attributeValue, issuer, false);
	}

	@Override
	public List decodeResult(ResultSet resultSet) throws PIPException {
		List listAttributes	= new ArrayList();
		for (String fieldName : this.mapFields.keySet()) {
			PIPRequest pipRequestField	= this.mapFields.get(fieldName);
			assert(pipRequestField != null);
			
			Attribute attribute	= this.getAttributeFromResultSet(resultSet, fieldName, pipRequestField);
			if (attribute != null) {
				listAttributes.add(attribute);
			}
		}
		return listAttributes;
	}

	@Override
	public void attributesRequired(Collection parameters) {
		for (PIPRequest parameter : this.parameters) {
			parameters.add(new StdPIPRequest(parameter.getCategory(), parameter.getAttributeId(), parameter.getDataTypeId(), parameter.getIssuer()));
		}
	}

	@Override
	public void attributesProvided(Collection attributes) {
		for (String key : this.mapFields.keySet()) {
			PIPRequest attribute = this.mapFields.get(key);
			attributes.add(new StdPIPRequest(attribute.getCategory(), 
											attribute.getAttributeId(), 
											attribute.getDataTypeId(), 
											(attribute.getIssuer() != null ? attribute.getIssuer() : this.defaultIssuer)));
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy