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

com.att.research.xacml.std.pip.engines.jdbc.JDBCEngine 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.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

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

import com.att.research.xacml.api.Attribute;
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.pip.StdMutablePIPResponse;
import com.att.research.xacml.std.pip.StdPIPResponse;
import com.att.research.xacml.std.pip.engines.StdConfigurableEngine;
import com.att.research.xacml.util.AttributeUtils;
import com.google.common.base.Splitter;
import com.google.common.cache.Cache;

/**
 * PIPEgineJDBC extends {@link com.att.research.xacml.std.pip.engines.StdConfigurableEngine} to implement a PIP that
 * retrieves XACML attributes from a database using JDBC.  This is a minimal implementation that does not do any caching of
 * results.  It does not perform JDBC connection pooling either.
 * 
 * @author Christopher A. Rath
 * @version $Revision$
 */
public class JDBCEngine extends StdConfigurableEngine {
	public static final String PROP_TYPE			= "type";
	public static final String PROP_JDBC_DRIVER		= "jdbc.driver";
	public static final String PROP_JDBC_URL		= "jdbc.url";
	public static final String PROP_JDBC_CONN		= "jdbc.conn";
	public static final String PROP_JDBC_CONN_USER	= "jdbc.conn.user";
	public static final String PROP_JDBC_CONN_PASS	= "jdbc.conn.password";
	public static final String PROP_RESOLVERS		= "resolvers";
	public static final String PROP_RESOLVER		= "resolver";
	public static final String PROP_CLASSNAME		= "classname";
	
	public static final String TYPE_JDBC			= "jdbc";
	public static final String TYPE_JNDI			= "jndi";
	
	protected Logger logger	= LoggerFactory.getLogger(this.getClass());
	private String type;
	private String jndiDataSource;
	private String jdbcDriverClass;
	private boolean jdbcDriverClassLoaded;
	private String jdbcUrl;
	private Properties jdbcConnProperties	= new Properties();
	private List jdbcResolvers	= new ArrayList();
	
	/**
	 * If the JDBC driver Class has not been loaded yet, do so now.
	 * 
	 * @throws ClassNotFoundException
	 */
	protected void loadDriverClass() throws ClassNotFoundException {
		if (!this.jdbcDriverClassLoaded) {
			synchronized(this) {
				if (!this.jdbcDriverClassLoaded) {
					Class.forName(this.jdbcDriverClass);
					this.jdbcDriverClassLoaded	= true;
				}
			}
		}
	}
	
	/**
	 * Creates a JDBC {@link java.sql.Connection} to the database.  Extensions to the JDBCEngine class can perform
	 * connection pooling or other connection reuse optimizations here.
	 * 
	 * @return a Connection to use to execute the query
	 * @throws PIPException if there is an error creating the JDBC Connection.
	 */
	protected Connection getConnection() throws PIPException {
		/*
		 * Check what type we are
		 */
		if (this.type.equals(TYPE_JDBC)) {
			return this.getJDBCConnection();
		} else {
			return this.getJNDIConnection();
		}
	}
	
	protected Connection getJDBCConnection() throws PIPException {
		/*
		 * Ensure the driver class is loaded
		 */
		try {
			this.loadDriverClass();
		} catch (ClassNotFoundException ex) {
			this.logger.error("ClassNotFoundException loading JDBC driver class '" + this.jdbcDriverClass + "'", ex);
			throw new PIPException("ClassNotFoundException loading JDBC driver class '" + this.jdbcDriverClass + "'", ex);
		}
		
		/*
		 * Try to create a new Connection
		 */
		Connection connectionResult	= null;
		try {
			connectionResult	= DriverManager.getConnection(this.jdbcUrl, this.jdbcConnProperties);
		} catch (SQLException ex) {
			this.logger.error("SQLException creating Connection", ex);
			throw new PIPException("SQLException creating Connection", ex);
		}
		
		return connectionResult;
	}
	
	protected Connection getJNDIConnection() throws PIPException {
		try {
			Context initialContext = new InitialContext();
			DataSource datasource = (DataSource) initialContext.lookup(this.jndiDataSource);
			if (datasource == null) {
				throw new PIPException("");
			}
			return datasource.getConnection();
		} catch (NamingException | SQLException e) {
			this.logger.error("JNDIException creating Connection", e);
			throw new PIPException("JNDIException creating Connection", e);
		}
	}
	
	protected void getAttributes(PIPRequest pipRequest, PIPFinder pipFinder, JDBCResolver jdbcResolver, StdMutablePIPResponse pipResponse) throws PIPException {
		/*
		 * First we need to get a PreparedStatement
		 */
		Connection connection = this.getConnection();
		PreparedStatement preparedStatement	= jdbcResolver.getPreparedStatement(this, pipRequest, pipFinder, connection);
		if (preparedStatement == null) {
			this.logger.debug(this.getName() + " does not handle " + pipRequest.toString());
			try {
				if (connection != null) {
					connection.close();
				}
			} catch (Exception e) {				
			}
			return;
		}
		
		/*
		 * Is it in the cache?
		 */
		this.logger.debug(preparedStatement.toString());
		Cache cache = this.getCache();
		if (cache != null) {
			// TODO - a cache key
		}
		
		/*
		 * Execute the prepared statement
		 */
		ResultSet resultSet		= null;
		try {
			resultSet	= preparedStatement.executeQuery();
		} catch (SQLException ex) {
			this.logger.error("SQLException executing query: " + ex.toString(), ex);
			// TODO: Should we re-throw the exception, or just return an empty response?
		}
		if (resultSet == null) {
			try {
				preparedStatement.close();
			} catch (SQLException e) {
				this.logger.error("SQLException closing preparedStatment: " + e.toString(), e);
			}
			try {
				if (connection != null) {
					connection.close();
				}
			} catch (Exception e) {				
			}
			return;
		}		
		try {
			/*
			 * Get all the results
			 */
			while (resultSet.next()) {
				List listAttributes	= jdbcResolver.decodeResult(resultSet);
				if (listAttributes != null) {
					pipResponse.addAttributes(listAttributes);
				}
			}
			/*
			 * Save it in the cache
			 */
			if (cache != null) {
				// TODO
			}
		} catch (SQLException ex) {
			this.logger.error("SQLException decoding results: " + ex.toString());
			// TODO: Should we re-throw the exception or just continue
		} finally {
			if (resultSet != null) {
				try {
					resultSet.close();
				} catch (SQLException e) {
					this.logger.error("SQLException closing resultSet: " + e.toString() + "  (May be memory leak)");
				}
			}
			try {
				preparedStatement.close();
			} catch (SQLException e) {
				this.logger.error("SQLException closing preparedStatement: " + e.toString() + "  (May be memory leak)");
			}
			try {
				connection.close();
			} catch (SQLException e) {
				this.logger.error("SQLException closing connection: " + e.toString() + "  (May be memory leak)");
			}
		}
	}

	@Override
	public PIPResponse getAttributes(PIPRequest pipRequest, PIPFinder pipFinder) throws PIPException {
		if (this.jdbcResolvers.size() == 0) {
			throw new IllegalStateException(this.getClass().getCanonicalName() + " is not configured");
		}
		
		StdMutablePIPResponse mutablePIPResponse	= new StdMutablePIPResponse();
		for (JDBCResolver jdbcResolver : this.jdbcResolvers) {
			this.getAttributes(pipRequest, pipFinder, jdbcResolver, mutablePIPResponse);
		}
		if (mutablePIPResponse.getAttributes().size() == 0) {
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("returning empty response");
			}
			return StdPIPResponse.PIP_RESPONSE_EMPTY;
		} else {
			if (this.logger.isDebugEnabled()) {
				this.logger.trace("Returning " + mutablePIPResponse.getAttributes().size() + " attributes");
				for (Attribute attribute : mutablePIPResponse.getAttributes()) {
					this.logger.debug(AttributeUtils.prettyPrint(attribute));
				}
			} else if (this.logger.isDebugEnabled()) {
//				this.logger.debug("Returning " + mutablePIPResponse.getAttributes().size() + " attributes");
//				this.logger.debug(mutablePIPResponse.getAttributes());
			}
			return new StdPIPResponse(mutablePIPResponse);
		}
	}
	
	/**
	 * Creates a new {@link com.att.research.xacml.std.pip.engines.jdbc.JDBCResolver} by looking up the "classname"
	 * property for the given String resolver ID and then calling its configure method.
	 * 
	 * @param resolverId the String identifier of the resolver to configure
	 * @param properties the Properties to search for the "classname" and any resolver-specific properties
	 * @throws PIPException if there is an error creating the JDBCResolver.
	 */
	protected void createResolver(String resolverId, Properties properties) throws PIPException {
		String propPrefix	= resolverId + ".";
		String resolverClassName	= properties.getProperty(propPrefix + PROP_CLASSNAME);
		if (resolverClassName == null || resolverClassName.length() == 0) {
			this.logger.error("No '" + propPrefix + PROP_CLASSNAME + "' property.");
			throw new PIPException("No '" + propPrefix + PROP_CLASSNAME + "' property.");
		}
		try {
			Class resolverClass	= Class.forName(resolverClassName);
			if (!JDBCResolver.class.isAssignableFrom(resolverClass)) {
				this.logger.error("JDBCResolver class " + propPrefix + " does not implement " + JDBCResolver.class.getCanonicalName());
				throw new PIPException("JDBCResolver class " + propPrefix + " does not implement " + JDBCResolver.class.getCanonicalName());
				
			}
			JDBCResolver jdbcResolver	= JDBCResolver.class.cast(resolverClass.newInstance());
			jdbcResolver.configure(resolverId, properties, this.getIssuer());
			this.jdbcResolvers.add(jdbcResolver);
		} catch (Exception ex) {
			this.logger.error("Exception creating JDBCResolver: " + ex.getMessage(), ex);
			throw new PIPException("Exception creating JDBCResolver", ex);
		}		
	}

	@Override
	public void configure(String id, Properties properties) throws PIPException {
		//
		// Our configurable properties
		//
		super.configure(id, properties);
		//
		// Prefix
		//
		String propPrefix	= id + ".";
		//
		// What is our type?
		//
		this.type = properties.getProperty(propPrefix + PROP_TYPE, TYPE_JDBC);
		//
		// These are mandatory for our engine to work.
		//
		if ((this.jdbcDriverClass = properties.getProperty(propPrefix + PROP_JDBC_DRIVER)) == null) {
			this.logger.error("No '" + propPrefix + PROP_JDBC_DRIVER + "' property");
			throw new PIPException("No '" + propPrefix + PROP_JDBC_DRIVER + "' property");
		}
		try {
			Class.forName(this.jdbcDriverClass);
		} catch (Exception ex) {
			this.logger.error("Exception instantiating JDBC driver class '" + this.jdbcDriverClass + "'", ex);
			throw new PIPException("Exception instantiating JDBC driver class '" + this.jdbcDriverClass + "'", ex);
		}
		
		if ((this.jdbcUrl = properties.getProperty(propPrefix + PROP_JDBC_URL)) == null) {
			this.logger.error("No '" + propPrefix + PROP_JDBC_URL + "' property");
			throw new PIPException("No '" + propPrefix + PROP_JDBC_URL + "' property");
		}
		//
		// Go through all our resolvers
		//
		String propResolverPrefix	= propPrefix + PROP_RESOLVERS;
		String stringProp = properties.getProperty(propResolverPrefix);
		if (stringProp == null || stringProp.isEmpty()) {
			this.logger.error("No '" + propResolverPrefix + "' property");
			throw new PIPException("No '" + propResolverPrefix + "' property");
		}
		for (String resolverId : Splitter.on(',').trimResults().omitEmptyStrings().split(stringProp)) {
			this.createResolver(propPrefix + PROP_RESOLVER + "." + resolverId, properties);
		}
		//
		// Check for these properties. They are not required.
		//
		if ((stringProp = properties.getProperty(propPrefix + PROP_JDBC_CONN_USER)) != null) {
			this.jdbcConnProperties.setProperty("user", stringProp);
		}
		if ((stringProp = properties.getProperty(propPrefix + PROP_JDBC_CONN_PASS)) != null) {
			this.jdbcConnProperties.setProperty("password", stringProp);
		}
		String jdbcConnPrefix	= propPrefix + PROP_JDBC_CONN;
		if ((stringProp = properties.getProperty(jdbcConnPrefix)) != null) {
			jdbcConnPrefix	= jdbcConnPrefix + ".";
			String[] connProperties	= stringProp.split("[,]",0);
			for (String connProperty : connProperties) {
				if ((stringProp = properties.getProperty(jdbcConnPrefix + connProperty)) != null) {
					this.jdbcConnProperties.setProperty(connProperty, stringProp);
				}
			}
		}
	}

	@Override
	public Collection attributesRequired() {
		Set attributes = new HashSet();
		for (JDBCResolver jdbcResolver : this.jdbcResolvers) {
			jdbcResolver.attributesRequired(attributes);
		}
		return attributes;
	}

	@Override
	public Collection attributesProvided() {
		Set attributes = new HashSet();
		for (JDBCResolver jdbcResolver : this.jdbcResolvers) {
			jdbcResolver.attributesProvided(attributes);
		}
		return attributes;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy