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

com.novartis.opensource.yada.QueryManager Maven / Gradle / Ivy

/**
 * Copyright 2016 Novartis Institutes for BioMedical Research Inc.
 * Licensed 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 com.novartis.opensource.yada;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.servlet.http.Cookie;
import javax.xml.soap.SOAPConnection;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.novartis.opensource.yada.adaptor.FileSystemAdaptor;
import com.novartis.opensource.yada.adaptor.JDBCAdaptor;
import com.novartis.opensource.yada.adaptor.RESTAdaptor;
import com.novartis.opensource.yada.adaptor.SOAPAdaptor;
import com.novartis.opensource.yada.adaptor.YADAAdaptorException;
import com.novartis.opensource.yada.util.QueryUtils;
import com.novartis.opensource.yada.util.YADAUtils;

/**
 * QueryManager is a workhorse class that performs essential query preparation
 * tasks prior to execution. These tasks include retrieving {@link YADAQuery}
 * objects using {@link Finder#getQuery(String)}, and creating global and
 * query-based data structures for storing and mapping source connections,
 * statements, and results.
 * 
 * @since 4.0.0
 * @author David Varon
 * @see Service#execute()
 * @see Service#handleRequest(javax.servlet.http.HttpServletRequest)
 */
public class QueryManager
{
	/**
	 * Local logger handle
	 */
	private static Logger l = Logger.getLogger(QueryManager.class);
	/**
	 * Local handle to configuration object
	 */
	private YADARequest yadaReq;
	/**
	 * Local handle to configuration object
	 */
	private JSONParams jsonParams;
	/**
	 * Index of queries passed in request
	 */
	private YADAQuery[] queries;
	/**
	 * Utility object
	 */
	private QueryUtils qutils = new QueryUtils();
	/**
	 * Map of sources (JNDI strings) to JDBC or SOAP connections
	 */
	private HashMap connectionMap = new HashMap<>();
	/**
	 * Map of SOAP query names to url strings
	 */
	private HashMap soapMap = new HashMap<>();
	/**
	 * Map of REST query names to url strings
	 */
	private HashMap urlMap = new HashMap<>();
	/**
	 * List of sources for which to defer commit execution, usu due to unsupported {@link ResultSet#HOLD_CURSORS_OVER_COMMIT}
	 */
	private List deferredCommits = new ArrayList<>();
	/**
	 * Set of sources for which to commit execution is required—effectively any JDBC source in a {@link YADAQuery} with
	 *  an {@code INSERT}, {@code UPDATE}, or {@code DELETE} statement 
	 */
	private Set requiredCommits = new HashSet<>();
	/**
	 * Constant equal to: {@value}
	 */
	@SuppressWarnings("unused")
	private final static String UNDEFINED = "undefined";
	/**
	 * Constant equal to: {@value}
	 */
	@SuppressWarnings("unused")
	private final static String NULLSTRING = "null";

	/**
	 * Constructor stores config object and JSONParams if necessary, then calls
	 * effectively bootstraps query management process by calling
	 * {@link #processQueries()}
	 * 
	 * @since 4.0.0
	 * @param yadaReq
	 *          YADA request configuration
	 * @throws YADAUnsupportedAdaptorException
	 *           when the adaptor can't be instantiated, or when it can't be found
	 * @throws YADAFinderException
	 *           when there is an issue retrieving a query from the YADA index
	 * @throws YADAConnectionException
	 *           when there is an issue opening a connection to a source
	 *           referenced by a query
	 * @throws YADAResourceException
	 *           when a query's source attribute can't be found in the application
	 *           context, or there is another problem with the context
	 * @throws YADAQueryConfigurationException
	 *           if request does not contain either a {@code qname} or {@code q},
	 *           or a {@code JSONParams} or {@code j} parameter
	 * @throws YADAAdaptorException
	 *           when a query cannot be built by the adaptor
	 * @throws YADARequestException
	 *           when filters are included in the request config, but can't be
	 *           converted into a JSONObject
	 * @throws YADAParserException
	 *           when a query code cannot be parsed successfully
	 */
	public QueryManager(YADARequest yadaReq) throws YADAQueryConfigurationException, YADAResourceException, YADAConnectionException, YADAFinderException, YADAUnsupportedAdaptorException, YADARequestException, YADAAdaptorException, YADAParserException
	{
//		setYADAReq(yadaReq);
//		if (YADAUtils.useJSONParams(yadaReq))
//			setJsonParams(yadaReq.getJsonParams());
//		processQueries();
	  processRequest(yadaReq);
	}
	
	
	/**
	 * Default constructor
	 * @since 7.1.0
	 */
	public QueryManager() {
	  
	}

	/**
   * A broker method which calls {@link #endowQuery(String)} (for standard param requests)
   * or {@link #endowQueries(JSONParams)} for json params requests, followed by setting
   * harmony maps, and 
   * {@link #prepQueriesForExecution()} in succession
   * 
   * @param yadaReq the {@link YADARequest} to process
   * @throws YADAFinderException
   *           when there is an issue retrieving a query from the YADA index
   * @throws YADAConnectionException
   *           when there is an issue opening a connection to a source
   *           referenced by a query
   * @throws YADAQueryConfigurationException
   *           if request does not contain either a {@code qname} or {@code q},
   *           or a {@code JSONParams} or {@code j} parameter
   * @throws YADAUnsupportedAdaptorException
   *           when the adaptor can't be instantiated, or when it can't be found
   * @throws YADAResourceException
   *           when a query's source attribute can't be found in the application
   *           context, or there is another problem with the context
   * @throws YADAAdaptorException
   *           when a query cannot be built by the adaptor
   * @throws YADARequestException
   *           when filters are included in the request config, but can't be
   *           converted into a JSONObject
   * @throws YADAParserException
   *           when query code cannot be parsed successfully
   * @since 7.1.0
   */
	private void processRequest(YADARequest yadaReq) throws YADAQueryConfigurationException, YADAConnectionException, YADAFinderException, YADAResourceException, YADAUnsupportedAdaptorException, YADARequestException, YADAAdaptorException, YADAParserException
	{
	  setYADAReq(yadaReq);
    if (YADAUtils.hasQname(getYADAReq()))
    {
      setQueries(new YADAQuery[] {endowQuery(getYADAReq().getQname())});
    }
    else if (YADAUtils.hasJSONParams(getYADAReq())) //TODO this is a redundant call, see the constructor
    {
      setJsonParams(yadaReq.getJsonParams());
      setQueries(endowQueries(getJsonParams()));
    }
    else
    {
      String msg = "Your request must contain a 'qname', 'q', 'JSONParams', or 'j' parameter.";
      throw new YADARequestException(msg);
    }
    setGlobalHarmonyMaps();
    setQueryHarmonyMaps();
    prepQueriesForExecution();
	}
	
	/**
	 * A broker method which calls {@link #endowQuery(String)} (for standard param requests)
	 * or {@link #endowQueries(JSONParams)} for json params requests, followed by
	 * {@link #prepQueriesForExecution()} in succession
	 * 
	 * @throws YADAFinderException
	 *           when there is an issue retrieving a query from the YADA index
	 * @throws YADAConnectionException
	 *           when there is an issue opening a connection to a source
	 *           referenced by a query
	 * @throws YADAQueryConfigurationException
	 *           if request does not contain either a {@code qname} or {@code q},
	 *           or a {@code JSONParams} or {@code j} parameter
	 * @throws YADAUnsupportedAdaptorException
	 *           when the adaptor can't be instantiated, or when it can't be found
	 * @throws YADAResourceException
	 *           when a query's source attribute can't be found in the application
	 *           context, or there is another problem with the context
	 * @throws YADAAdaptorException
	 *           when a query cannot be built by the adaptor
	 * @throws YADARequestException
	 *           when filters are included in the request config, but can't be
	 *           converted into a JSONObject
	 * @throws YADAParserException
	 *           when query code cannot be parsed successfully
	 * @since 4.0.0
	 * @deprecated as of 7.1.0
	 */
	@SuppressWarnings("unused")
  @Deprecated
	private void processQueries() throws YADAQueryConfigurationException, YADAConnectionException, YADAFinderException, YADAResourceException, YADAUnsupportedAdaptorException, YADARequestException, YADAAdaptorException, YADAParserException
	{
		if (YADAUtils.hasJSONParams(this.yadaReq)) //TODO this is a redundant call, see the constructor
		{
			setQueries(endowQueries(getJsonParams()));
		} 
		else if (YADAUtils.hasQname(this.yadaReq))
		{
			setQueries(new YADAQuery[] {endowQuery(this.yadaReq.getQname())});
		} 
		else
		{
			String msg = "Your request must contain a 'qname', 'q', 'JSONParams', or 'j' parameter.";
			throw new YADARequestException(msg);
		}
		setGlobalHarmonyMaps();
		setQueryHarmonyMaps();
		prepQueriesForExecution();
	}
	
	/**
	 * Create a composite harmony map for all queries based on either {@link YADARequest#getHarmonyMap()} 
	 * or the harmony maps stored in each query via param {@link YADAQuery#getParam(String)} 
	 * named {@link YADARequest#PS_HARMONYMAP}.  This object, consistent in each request, enables inclusion
	 * of both mapped and unmapped columns in a single result, specifically necessary for 
	 * {@link YADARequest#FORMAT_CSV} and other delimited formats.
	 * @since 6.1.0
	 */
	private void setGlobalHarmonyMaps() 
	{
	  JSONArray  reqHm            = this.yadaReq.getHarmonyMap();
	  JSONObject globalHarmonyMap = new JSONObject();
	  if(null == reqHm)
	  {
	    for(YADAQuery yq : getQueries())
	    {
	      YADAParam p = yq.getParam(YADARequest.PS_HARMONYMAP);
	      if(p != null)
	      {
	        JSONObject j = new JSONObject(p.getValue());
	        if(j.length() > 0)
	        {
	          globalHarmonyMap = populateGlobalHarmonyMap(globalHarmonyMap, j);
	        }
	      }
	    }
	    for(YADAQuery yq : getQueries())
	      yq.setGlobalHarmonyMap(globalHarmonyMap);
	  }
	  else
	  {
	    for(int i=0;i 0)
        {
          globalHarmonyMap = populateGlobalHarmonyMap(globalHarmonyMap, j);
	      }
      }
	    for(YADAQuery yq : getQueries())
	      yq.setGlobalHarmonyMap(globalHarmonyMap);
	  }  
	}
	
	/**
	 * Populates a {@link JSONObject} with the unique set of keys, i.e., original column or field names 
	 * passed into the request in {@link JSONParams} or in the {@link YADARequest#PS_HARMONYMAP} parameter.
	 * 
	 * @param global The {@link JSONObject} to populate
	 * @param local The {@link JSONObject} containing the key/value pairs to transfer to {@code global}
	 * @return the {@code global} {@link JSONObject} containing the unique set of keys (and values)
	 * @since 6.1.0
	 */
	private JSONObject populateGlobalHarmonyMap(JSONObject global, JSONObject local)
	{
	  for(String key : JSONObject.getNames(local))
    {
      try
      {
        global.putOnce(key, local.get(key));
      }
      catch(JSONException e)
      {
        String msg = "Key ["+key+"] already exists in global harmony map.";
        l.warn(msg);
      }
    }
	  return global;
	}
	
	/**
	 * Checks for a {@code harmonyMap} or {@code h} spec in the {@link #yadaReq}. If {@code null}, 
	 * iterates over the queries in the request, identifying those that have embedded {@code h} specs
	 * and those that do not. If any queries contain specs, those that do not are provided with empty maps.
	 * 
	 * @throws YADARequestException when the resulting harmonyMap is non-compliant
	 * @since 6.1.0
	 */
	private void setQueryHarmonyMaps() throws YADARequestException 
	{
  	if(this.yadaReq.getHarmonyMap() == null)
  	{
  	  ArrayList hasMap = new ArrayList<>();
  	  ArrayList noMap  = new ArrayList<>();
  	  for(YADAQuery yq : this.queries)
  	  {
  	    YADAParam p = yq.getParam(YADARequest.PS_HARMONYMAP);
  	    if(p != null)
  	    {
    	    hasMap.add(new JSONObject(p.getValue()));
  	    }
  	    else
  	    {
  	      noMap.add(yq);
  	    }
  	  }
  	  if(hasMap.size() > 0)
  	  {
  	    for(YADAQuery yq : noMap)
  	    {
  	      YADAParam param = new YADAParam(YADARequest.PS_HARMONYMAP,"{}",YADAParam.QUERY,YADAParam.OVERRIDEABLE);
  	      yq.addParam(param);
  	    }
  	  }
  	}
	}

	/**
	 * Adds the JDBC or SOAP connection object for {@code yq} to the internal
	 * index. If the connection is not yet in the index, it is created from the
	 * source stored in the {@code yq} object.  If the protocol value in the query
	 * is not SOAP or JDBC, the method exits silently
	 * 
	 * @param yq
	 *          the query object containing the source string for setting the
	 *          connection
	 * @throws YADAConnectionException
	 *           when the connection to the source in the query cannot be set
	 */
	private void storeConnection(YADAQuery yq) throws YADAConnectionException
	{
		String source = yq.getSource();
		if (!this.connectionMap.containsKey(source))
		{
			yq.setConnection();
			this.connectionMap.put(source, yq.getConnection());
		} 
		else
		{
			if (yq.getProtocol().equals(Parser.JDBC))
				yq.setConnection((Connection)this.connectionMap.get(source));
			else if (yq.getProtocol().equals(Parser.SOAP))
				yq.setSOAPConnection((SOAPConnection)this.connectionMap.get(source));
		}
	}

	/**
	 * Executes a query-level commit on the connection stored in the YADAQuery
	 * referenced by the parameter. If the connection object returned by the query
	 * is not a JDBC connection, but, for instance, a SOAPConnection, the error
	 * will be caught and handled gracefully.
	 * 
	 * @param yq
	 *          the query containing the statements to commit
	 * @throws YADAConnectionException
	 *           when the commit fails
	 */
	public void commit(YADAQuery yq) throws YADAConnectionException
	{
		try
		{
			if(this.requiredCommits.contains(yq.getSource()))
			{
				Connection connection = (Connection)yq.getConnection();
				if(connection.getHoldability() == ResultSet.HOLD_CURSORS_OVER_COMMIT)
				{
					connection.commit();
					int    count = yq.getResult().getTotalResultCount();
					String rows  = count == 1 ? "row" : "rows"; 
					String msg   = "\n------------------------------------------------------------\n";
					msg         += "   Commit successful on connection to ["+yq.getSource()+"] ("+count+" "+rows+")\n";
					msg         += "------------------------------------------------------------\n";
					l.debug(msg);
				}
				else
				{
					deferCommit(yq.getSource());
				}
			}
		} 
		catch (SQLException e)
		{
			String msg = "Unable to commit transaction on ["+yq.getSource()+"].";
			throw new YADAConnectionException(msg, e);
		} 
		catch (ClassCastException e)
		{
			l.info("Connection to ["+yq.getSource()+"] is not a JDBC connection (it's probably SOAP.) No commit was attempted.");
		}
	}

	/**
	 * Executes a commit on all connections created during processing of the
	 * current request.
	 * 
	 * @throws YADAConnectionException
	 *           when the commit fails
	 */
	public void commit() throws YADAConnectionException
	{
		if (this.connectionMap != null && this.connectionMap.keySet().size() > 0)
		{
			//TODO			int    totalCount = 0;
			String source     = "";
			for(Iterator iterator = this.requiredCommits.iterator(); iterator.hasNext();)
			{
				try
				{
					source = iterator.next();
					Connection connection = (Connection)this.connectionMap.get(source);
					if(connection.getHoldability() == ResultSet.HOLD_CURSORS_OVER_COMMIT)
					{
						connection.commit();
						String msg = "\n------------------------------------------------------------\n";
						msg += "   Commit successful on ["+source+"].\n";
						msg += "------------------------------------------------------------\n";
						l.info(msg);
					}
					else
					{
						deferCommit(source);
					}
				}
				catch (SQLException e)
				{
					String msg = "Unable to commit transaction on ["+source+"].";
					throw new YADAConnectionException(msg, e);
				} 
				catch (ClassCastException e)
				{
					l.info("Connection to ["+source+"] is not a JDBC connection (it's probably SOAP.)  No commit was attempted.");
				} 
			}
 		}
	}
	
	/**
	 * Adds {@code source} to the internal {@code #deferredCommits} list for execution of commit on the connection after
	 * results are parsed.
	 * @param source the jndi path of the source to commit later
	 * @since 4.2.0
	 */
	private void deferCommit(String source)
	{
		this.deferredCommits.add(source);
		String msg = "Commit deferred on ["+source+"]. This is done, most likely, because the JDBC driver for this source does not support holdability.";
		l.info(msg);
	}

	/**
	 * This method attempts (and usually suceeds) to close all JDBC resources
	 * opened during processing of the current request, including
	 * {@link ResultSet}s {@link java.sql.PreparedStatement}s and
	 * {@link java.sql.CallableStatement}s using the utility methods in
	 * {@link com.novartis.opensource.yada.ConnectionFactory}
	 * 
	 * @throws YADAConnectionException
	 *           when there is a problem closing any of the resources
	 * @see ConnectionFactory#releaseResources(ResultSet)
	 * @see ConnectionFactory#releaseResources(java.sql.Statement)
	 */
	public void releaseResources() throws YADAConnectionException
	{
		for (String source : this.deferredCommits)
		{
			try
			{
				Connection connection = (Connection)this.connectionMap.get(source);
				connection.commit();
				String msg = "\n------------------------------------------------------------\n";
				msg += "   Commit successful on ["+source+"].\n";
				msg += "------------------------------------------------------------\n";
				l.info(msg);
			}
			catch (SQLException e)
			{
				String msg = "\n------------------------------------------------------------\n";
				msg += "   Unable to commit transaction on ["+source+"].\n";
				msg += "------------------------------------------------------------\n";
				l.error(msg); //TODO should there be a rollback message here?
			} 
		}
		for (YADAQuery yq : this.getQueries())
		{
			YADAQueryResult yqr = yq.getResult();
			if (yqr != null && yqr.getResults() != null)
			{
				for (Object result : yqr.getResults())
				{
					if (result instanceof ResultSet)
					{
						ConnectionFactory.releaseResources((ResultSet)result);
					}
				}
			}
			if (yq.getPstmt() != null && yq.getPstmt().size() > 0)
			{
				for (PreparedStatement p : yq.getPstmt())
				{
					ConnectionFactory.releaseResources(p);
					l.debug("PreparedStatement removed from map.");
				}
			}
			if (yq.getPstmtForCount() != null && yq.getPstmtForCount().values()
																							.size() > 0)
			{
				for (PreparedStatement p : yq.getPstmtForCount().values())
				{
					ConnectionFactory.releaseResources(p);
				}
			}
			if (yq.getCstmt() != null && yq.getCstmt().size() > 0)
			{
				for (CallableStatement c : yq.getCstmt())
				{
					ConnectionFactory.releaseResources(c);
				}
			}
		}
		this.connectionMap.clear();
		l.debug("QueryManager connection map has been cleared.");
	}

	/**
	 * Adds the SQL function statement to the internal index
	 * 
	 * @param yq
	 *          the query object
	 * @param code
	 *          the raw code
	 */
	private void storeCallableStatement(YADAQuery yq, String code)
	{
			yq.addCstmt(this.qutils.getCallableStatement(	code,
																										(Connection)yq.getConnection()));
	}

	/**
	 * Adds the JDBC statement to the internal index
	 * 
	 * @param yq
	 *          the query containing the {@code code} and the index to which to
	 *          add the statement
	 * @param code
	 *          the SQL to map to the statement
	 * @throws YADAConnectionException
	 *           when the statement is not yet in the map, and the connection
	 *           deliver it
	 */
	private void storePreparedStatement(YADAQuery yq, String code) throws YADAConnectionException
	{
			PreparedStatement p = this.qutils.getPreparedStatement(code,(Connection)yq.getConnection());
			yq.addPstmt(p);
	}
	
	/**
   * Adds the JDBC statement to the internal index at the specified position
   * @param row the index of {@link YADAQuery#pstmt} at which to store the {@link PreparedStatement}
   * @param yq the query containing the {@code code} and the index to which to
   *          add the statement
   * @param code the SQL to map to the statement
   * @throws YADAConnectionException when the statement is not yet in the map, and the connection
   *           deliver it
   * @since 7.0.0
   */
  private void storePreparedStatement(YADAQuery yq, String code, int row) throws YADAConnectionException
  {
      PreparedStatement p = this.qutils.getPreparedStatement(code,(Connection)yq.getConnection());
      yq.addPstmt(p,row);
  }

	/**
	 * Adds the JDBC statement to the internal index
	 * 
	 * @param yq
	 *          the query containing the {@code code} and the index to which to
	 *          add the statement
	 * @param p
	 *          the statement for the data query, serving as a key for the count
	 *          statement in the map
	 * @param code
	 *          the SQL to map to the statement
	 * @throws YADAConnectionException
	 *           when the statement is not yet in the map, and the connection
	 *           deliver it
	 */
	private void storePreparedStatementForCount(YADAQuery yq,
																							PreparedStatement p, String code) throws YADAConnectionException
	{
		// TODO this won't work with CountOnly
			PreparedStatement pc = this.qutils.getPreparedStatement(code,
																															(Connection)yq.getConnection());
			yq.addPstmtForCount(p, pc);
	}

	/**
	 * Adds the SOAP message to the internal index
	 * 
	 * @param yq
	 *          the query containing the code and index
	 * @param code
	 *          the soap message
	 */
	private void storeSoapMessage(YADAQuery yq, String code)
	{
		if (this.soapMap.containsKey(code))
		{
			yq.addSoap(this.soapMap.get(code));
		} else
		{
			yq.addSoap(this.qutils.getSoap(code));
		}
	}

	/**
	 * Adds the REST url string to the internal index
	 * 
	 * @param yq
	 *          the query containing the code and index
	 * @param code
	 *          the rest url string
	 */
	private void storeRestQuery(YADAQuery yq, String code)
	{
		if (this.urlMap.containsKey(code))
		{
			yq.addUrl(this.urlMap.get(code));
		} else
		{
			yq.addUrl(code);
		}
	}
	
	/**
	 * Prepares the query for execution by retrieving the wrapped query code, amending
	 * it if needed, as prescribed by request parameters, and links the query statement
	 * with it's database connection.
	 * 
   * @since 7.0.0
   * @throws YADAResourceException
   *           when a query's source attribute can't be found in the application
   *           context, or there is another problem with the context
   * @throws YADAUnsupportedAdaptorException
   *           when there is no adaptor available for the source or protocol or
   *           the intended adaptor can't be instantiated
   * @throws YADAConnectionException
   *           when a connection to the source can't be opened
   * @throws YADARequestException
   *           when filters are included in the request config, but can't be
   *           converted into a JSONObject
   * @throws YADAAdaptorException
   *           when the query cannot be built by the adaptor
   * @throws YADAParserException
   *           when the query code cannot be parsed successfully
   */
	void prepQueryForExecution(YADAQuery yq) throws YADAResourceException, YADAUnsupportedAdaptorException, YADAConnectionException, YADARequestException, YADAAdaptorException, YADAParserException
	{

    String source        = yq.getSource();
    String conformedCode = yq.getConformedCode();
    String wrappedCode   = "";
    int    dataSize      = yq.getData().size() > 0 ? yq.getData().size() : 1;
    if(this.qutils.requiresConnection(yq))
    { 
      storeConnection(yq);
    }
    
    this.qutils.processStatement(yq);
    
    if(yq.getProtocol().equals(Parser.JDBC))
    {
      if(yq.getType().equals(Parser.CALL))
      {
        for (int row = 0; row < dataSize; row++)
        {
          wrappedCode = ((JDBCAdaptor)yq.getAdaptor()).buildCall(conformedCode).toString();
          String msg  = "\n------------------------------------------------------------";
                 msg += "\n   Callable statement to execute:";
                 msg += "\n------------------------------------------------------------\n";
                 msg += wrappedCode.toString() + "\n";
          l.debug(msg);
          storeCallableStatement(yq, wrappedCode);
        }
        this.requiredCommits.add(yq.getSource());
      }
      else
      {
        boolean    count     = Boolean.valueOf(yq.getYADAQueryParamValue(YADARequest.PS_COUNT)[0]).booleanValue();
        boolean    countOnly = Boolean.valueOf(yq.getYADAQueryParamValue(YADARequest.PS_COUNTONLY)[0]).booleanValue();
        int        pageStart = Integer.valueOf(yq.getYADAQueryParamValue(YADARequest.PS_PAGESTART)[0]).intValue();
        int        pageSize  = Integer.valueOf(yq.getYADAQueryParamValue(YADARequest.PS_PAGESIZE)[0]).intValue();
        if (pageSize == -1)
          pageSize = YADAUtils.ONE_BILLION;
        int        firstRow  = 1 + (pageStart * pageSize) - pageSize;
        String     sortOrder = yq.getYADAQueryParamValue(YADARequest.PS_SORTORDER)[0];
        String     sortKey   = "";
        JSONObject filters   = null;
        
        if (yq.hasParamValue(YADARequest.PS_SORTKEY))
        {
          sortKey = yq.getYADAQueryParamValue(YADARequest.PS_SORTKEY)[0];
        }
        
        try
        {
          if (yq.hasParamValue(YADARequest.PS_FILTERS))
          {
            filters = new JSONObject(yq.getYADAQueryParamValue(YADARequest.PS_FILTERS)[0]);
          }
        } 
        catch (JSONException e)
        {
          String msg = "Error while getting filters from parameters.";
          throw new YADARequestException(msg, e);
        }
        
        for (int row = 0; row < dataSize; row++)
        {
          if(yq.getInList() != null && yq.getInList().size() > 0)
          {
            conformedCode = this.qutils.getConformedCode(this.qutils.processInList(yq,row));
          }
          
          if(yq.getType().equals(Parser.SELECT))
          {
            wrappedCode = ((JDBCAdaptor)yq.getAdaptor()).buildSelect( conformedCode,
                                                                      sortKey,
                                                                      sortOrder,
                                                                      firstRow,
                                                                      pageSize,
                                                                      filters).toString();
            String msg  = "\n------------------------------------------------------------";
                   msg += "\n   SELECT statement to execute:";
                   msg += "\n------------------------------------------------------------\n";
                   msg += wrappedCode.toString() + "\n";
            l.debug(msg);
          }
          else // INSERT, UPDATE, DELETE
          {
            wrappedCode = conformedCode;
            this.requiredCommits.add(yq.getSource());
            String msg  = "\n------------------------------------------------------------";
                   msg += "\n   INSERT/UPDATE/DELETE statement to execute:";
                   msg += "\n------------------------------------------------------------\n";
                   msg += wrappedCode.toString() + "\n";
            l.debug(msg);
          }
          
          storePreparedStatement(yq, wrappedCode, row);
          if (yq.getType().equals(Parser.SELECT) && (count || countOnly))
          {
            wrappedCode = ((JDBCAdaptor)yq.getAdaptor()).buildSelectCount(conformedCode,filters).toString();
            String msg  = "\n------------------------------------------------------------";
                   msg += "\n   SELECT COUNT statement to execute:";
                   msg += "\n------------------------------------------------------------\n";
                   msg += wrappedCode.toString() + "\n";
            l.debug(msg);
            storePreparedStatementForCount(yq, yq.getPstmt(row), wrappedCode);
          }
          
          if(yq.getStatement() != null)
            this.qutils.setPositionalParameterValues(yq, row);
          else
            this.qutils.setValsInPosition(yq, row);
        } // end data loop
      } // end if callable
    } // end if JDBC
    else if(yq.getProtocol().equals(Parser.SOAP)
        || yq.getProtocol().equals(Parser.REST)
        || yq.getProtocol().equals(Parser.FILE))
    {
      if(yq.getType().equals(Parser.SOAP))
      {
        yq.setSource(source.replace(SOAPAdaptor.PROTOCOL_SOAP,SOAPAdaptor.PROTOCOL_HTTP));
        for (int row = 0; row < dataSize; row++)
        {
          wrappedCode = ((SOAPAdaptor)yq.getAdaptor()).build(yq);
          storeSoapMessage(yq, wrappedCode);
          this.qutils.setValsInPosition(yq, row);
        }
      }
      else if(yq.getType().equals(Parser.REST))
      {
        for (int row = 0; row < dataSize; row++)
        {
          wrappedCode = ((RESTAdaptor)yq.getAdaptor()).build(yq);
          storeRestQuery(yq, wrappedCode);
          this.qutils.setValsInPosition(yq, row);
        }
      }
      else // filesystem
      {
        for (int row = 0; row < dataSize; row++)
        {
          wrappedCode = ((FileSystemAdaptor)yq.getAdaptor()).build(yq);
          storeRestQuery(yq, wrappedCode);
          this.qutils.setValsInPosition(yq, row);
        }
      }
    }
    else
    {
      String msg = "The query you are attempting to execute requires ";
      msg += "a protocol or class that is not supported.  This ";
      msg += "could be the result of a configuration issue.";
      throw new YADAUnsupportedAdaptorException(msg);
    } // end protocols
	}

	/**
	 * @since 4.0.0
	 * @throws YADAResourceException
	 *           when a query's source attribute can't be found in the application
	 *           context, or there is another problem with the context
	 * @throws YADAUnsupportedAdaptorException
	 *           when there is no adaptor available for the source or protocol or
	 *           the intended adaptor can't be instantiated
	 * @throws YADAConnectionException
	 *           when a connection to the source can't be opened
	 * @throws YADARequestException
	 *           when filters are included in the request config, but can't be
	 *           converted into a JSONObject
	 * @throws YADAAdaptorException
	 *           when the query cannot be built by the adaptor
	 * @throws YADAParserException
	 *           when the query code cannot be parsed successfully
	 */
	private void prepQueriesForExecution() throws YADAResourceException, YADAUnsupportedAdaptorException, YADAConnectionException, YADARequestException, YADAAdaptorException, YADAParserException
	{
		for (YADAQuery yq : this.getQueries())
		{
		  prepQueryForExecution(yq);
		} // query loop
	} 

	/**
	 * Populates the data and parameter storage in the query object, using values passed in request object
	 * @since 4.0.0
	 * @param jSONParams
	 *          the request param containing the query information to process
	 * @return array of {@link YADAQuery} objects corresponding to JSONParams entries
	 * @throws YADAFinderException
	 *           when a query in {@code jsonParams} can't be found in the YADA
	 *           index
	 * @throws YADAConnectionException
	 *           when a connection to the YADA index can't be established
	 * @throws YADAQueryConfigurationException
	 *           the the YADA request is malformed
 	 * @throws YADAUnsupportedAdaptorException when the adaptor attached to the query object can't be found or instantiated
	 * @throws YADAResourceException when the query {@code q} can't be found in the index
 
	 */
	YADAQuery[] endowQueries(JSONParams jSONParams) throws YADAConnectionException, YADAFinderException, YADAQueryConfigurationException, YADAResourceException, YADAUnsupportedAdaptorException
	{
		Iterator jpIter = jSONParams.keySet().iterator();
		int index = 0;
		YADAQuery[] yqs = new YADAQuery[jSONParams.size()];
		while (jpIter.hasNext())
		{
			String qname = jpIter.next();
			
			YADAQuery yq = new Finder().getQuery(qname,this.getUpdateStats());
			yqs[index++] = endowQuery(yq,jSONParams.get(qname));
		}
		return yqs;
	}
	
	/**
	 * Populates the data and parameter storage in the {@code yq} object, using values passed {@code entry}
	 * @param yq the {@link YADAQuery} to augment
	 * @param entry the {@link JSONParamsEntry} containing the values to store in the query
	 * @return the augmented version of {@code yq}
	 * @since 4.2.0
	 * @throws YADAQueryConfigurationException
	 *           the the YADA request is malformed
 	 * @throws YADAUnsupportedAdaptorException when the adaptor attached to the query object can't be found or instantiated
	 * @throws YADAResourceException when the query {@code q} can't be found in the index
 
	 */
	YADAQuery endowQuery(YADAQuery yq, JSONParamsEntry entry) throws YADAQueryConfigurationException, YADAResourceException, YADAUnsupportedAdaptorException 
	{
		yq.addAllData(entry.getData());
		yq.addYADAQueryParams(entry.getParams());
		yq.setParameterizedColumns(yq.getColumnNameArray());
		return endowQuery(yq);
	}

	/**
	 * Populates the data and parameter storage in the query object, using values passed in request object.
	 * When the request has a qname and params, it is for a singular query.
	 * 
	 * @param q the name of the query to process
	 * @return a {@link YADAQuery} object retrieved from the YADA index corresponding to {@code q}
	 * 
	 * @throws YADAFinderException
	 *           when the query {@code q} can't be found in the YADA index
	 * @throws YADAConnectionException
	 *           when a connection to the YADA index can't be established
	 * @throws YADAQueryConfigurationException
	 *           the the YADA request is malformed
	 * @throws YADAUnsupportedAdaptorException when the adaptor attached to the query object can't be found or instantiated
	 * @throws YADAResourceException when the query {@code q} can't be found in the index
	 * @since 4.0.0
	 */
	YADAQuery endowQuery(String q) throws YADAConnectionException, YADAFinderException, YADAQueryConfigurationException, YADAResourceException, YADAUnsupportedAdaptorException
	{
		YADAQuery yq = new Finder().getQuery(q,this.getUpdateStats());
		LinkedHashMap data = new LinkedHashMap<>();
    String[][] params = this.yadaReq.getParams();
    if (params != null)
    {
      for (int i = 0; i < params.length; i++)
      {
        data.put(QueryUtils.YADA_COLUMN + String.valueOf(i + 1), params[i]);
      }
      yq.addData(data);
      yq.setParameterizedColumns(yq.getColumnNameArray());
    }
    yq.addParam(YADARequest.PS_QNAME, yq.getQname());
		endowQuery(yq);
		return yq;
	}

	/**
	 * Populates the data and parameter storage in the query object, using values passed in request object
	 * @since 4.0.0
	 * @param yq
	 *          the query object to be processed
	 * @return {@code yq}, now endowed with metadata
	 * @throws YADAFinderException
	 *           when the name of the query in {@code yq} can't be found in the
	 *           YADA index
	 * @throws YADAQueryConfigurationException
	 *           the the YADA request is malformed
	 * @throws YADAUnsupportedAdaptorException when the adaptor attached to the query object can't be found or instantiated
	 * @throws YADAResourceException when the query {@code q} can't be found in the index
	 */
	YADAQuery endowQuery(YADAQuery yq) throws YADAQueryConfigurationException, YADAResourceException, YADAUnsupportedAdaptorException
	{
	  int index = 0;
	  if (getJsonParams() != null)
	    index = ArrayUtils.indexOf(getJsonParams().getKeys(), yq.getQname());
		yq.addRequestParams(this.yadaReq.getRequestParamsForQueries(),index);
		yq.setAdaptorClass(this.qutils.getAdaptorClass(yq.getApp()));
		if(RESTAdaptor.class.equals(yq.getAdaptorClass()) && this.yadaReq.hasCookies())
    {
      for(String cookieName : this.yadaReq.getCookies())
      {
        for(Cookie cookie : this.yadaReq.getRequest().getCookies())
        {
          if(cookie.getName().equals(cookieName))
          {
            yq.addCookie(cookieName, Base64.encodeBase64String(Base64.decodeBase64(cookie.getValue().getBytes())));
          }
        }
      }
    }
		
		//TODO handle missing params exceptions here, throw YADARequestException
		//TODO review instances where YADAQueryConfigurationException is thrown
		this.qutils.setProtocol(yq);
		yq.setAdaptor(this.qutils.getAdaptor(yq.getAdaptorClass(), this.yadaReq));
		yq.setConformedCode(this.qutils.getConformedCode(yq.getYADACode()));
		for (int row = 0; row < yq.getData().size(); row++)
		{
		  // TODO perhaps move this functionality to the deparsing step? 
		  yq.addDataTypes(row, this.qutils.getDataTypes(yq.getYADACode()));
		  int paramCount = yq.getDataTypes().get(0).length;
		  yq.addParamCount(row, paramCount);
		}
		return yq;
	}

	/**
	 * Accessor for array of {@link YADAQuery} objects.
	 * 
	 * @since 4.0.0
	 * @return array of YADAQuery objects
	 */
	public YADAQuery[] getQueries()
	{
		return this.queries;
	}

	/**
	 * Accessor for {@link YADAQuery} at index.
	 * 
	 * @param index
	 *          the position of the desired query in the internal array
	 * @return YADAQuery at the desired index
	 */
	public YADAQuery getQuery(int index)
	{
		return this.getQueries()[index];
	}
	
	/**
	 * Returns the value of {@link YADARequest#PS_UPDATE_STATS} 
	 * @return {@code true} by default, or {@code false} if set deliberately in the request
	 */
	private boolean getUpdateStats() {
		return this.yadaReq.getUpdateStats();
	}
	
	/**
	 * Standard accessor.
	 * @return the local {@link YADARequest}
	 */
	private YADARequest getYADAReq() {
	  return this.yadaReq;
	}

	/**
	 * Standard mutator for variable
	 * 
	 * @param yadaReq
	 *          YADA request configuration
	 */
	private void setYADAReq(YADARequest yadaReq)
	{
		this.yadaReq = yadaReq;
	}

	/**
	 * Standard accessor for variable
	 * 
	 * @return the jsonParams object
	 */
	private JSONParams getJsonParams()
	{
		return this.jsonParams;
	}

	/**
	 * Standard mutator for variable
	 * 
	 * @param jsonParams
	 *          the config object to set
	 */
	private void setJsonParams(JSONParams jsonParams)
	{
		this.jsonParams = jsonParams;
	}

	/**
	 * @param queries the queries to set
	 */
	private void setQueries(YADAQuery[] queries)
	{
		this.queries = queries;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy