<commit_before>package org.oasis.datacore.rest.server.parsing.service.impl;

import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;

import org.oasis.datacore.core.meta.model.DCField;
import org.oasis.datacore.core.meta.model.DCFieldTypeEnum;
import org.oasis.datacore.core.meta.model.DCListField;
import org.oasis.datacore.rest.server.parsing.exception.ResourceParsingException;
import org.oasis.datacore.rest.server.parsing.model.DCQueryParsingContext;
import org.oasis.datacore.rest.server.parsing.model.QueryOperatorsEnum;
import org.oasis.datacore.rest.server.parsing.service.QueryParsingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

@Service
public class QueryParsingServiceImpl implements QueryParsingService {

	@Autowired
	@Qualifier("datacoreApiServer.objectMapper")
	public ObjectMapper mapper;
	
	public void parseCriteriaFromQueryParameter(String fieldPath, String operatorAndValue, 
												DCField dcField, DCQueryParsingContext queryParsingContext) 
												throws ResourceParsingException {
		// TODO (mongo)operator for error & in parse ?
		String entityFieldPath = "_p." + fieldPath;
		
		// QueryOperatorEnum = operator name
		// Integer = operator size
		SimpleEntry<QueryOperatorsEnum, Integer> operatorEntry = QueryOperatorsEnum.getEnumFromOperator(operatorAndValue);
		QueryOperatorsEnum operatorEnum = operatorEntry.getKey();
		int operatorSize = operatorEntry.getValue();
		// We check if the selected operator is suitable for the type of DCField
		isQueryOperatorSuitableForField(dcField, operatorEnum);
		// If the operator is a sort (e.g. name=-) there is no value !
		// So we don't need to substring to remove the operator from the value as there is no value
		String queryValue = null;
		QueryOperatorsEnum sortEnum = null;
		Object parsedData = null;
		if (operatorEnum != null) {
			if (!QueryOperatorsEnum.SORT_ASC.name().equals(operatorEnum.name()) && !QueryOperatorsEnum.SORT_DESC.name().equals(operatorEnum.name())) {
				// We get the value of the selected operator
				queryValue = operatorAndValue.substring(operatorSize);
				// We need to know if we need to sort the value
				sortEnum = isSortNeeded(queryValue);
				// If field is sorted we need to remove the last char (+ or -)
				if(sortEnum != null) {
					queryValue = queryValue.substring(0, queryValue.length()-1);
				}
				// We get the DCFieldType enum according to the type of the field
				DCFieldTypeEnum dcFieldTypeEnum = DCFieldTypeEnum.getEnumFromStringType(dcField.getType());
				if(dcFieldTypeEnum == null) {
					throw new ResourceParsingException("can't find the type of " + dcField.getName());
				}
				// Then we parse the data
				parsedData = parseValue(dcFieldTypeEnum, queryValue);
				if(parsedData == null) {
					throw new ResourceParsingException("Field " + dcField.getName() + " cannot be parsed in " + dcFieldTypeEnum.getType() + " format");
				}
			}
		} else {
			throw new ResourceParsingException("Query operator wasn't identified for query parameter " + operatorAndValue);
		}
		switch(operatorEnum) {
		
			case ALL: 
				// TODO LATER $all with $elemMatch
				// TODO same fieldPath for mongodb ??
				queryParsingContext.getCriteria().and(entityFieldPath).all(parsedData);
				break;
				
			case ELEM_MATCH:
				if (!"list".equals(dcField.getType())) {
					throw new ResourceParsingException("$elemMatch can only be applied to a list but found " + dcField.getType() + " Field");
				}
				// parsing using the latest upmost list field :
				// WARNING the first element in the array must be selective, because all documents
				// containing it are scanned
		        // TODO which syntax ??
		        // TODO alt 1 : don't support it as a different operator but auto use it on list fields
		        // (save if ex. boolean mode...) ; and add OR syntax here using ex. &join=OR syntax NO WRONG 
		        // alt 2 : support it explicitly, with pure mongo syntax NO NOT IN HTTP GET QUERY
		        // alt 3 : support it explicitly, with Datacore query syntax MAYBE ex. :
		        
		        // (((TODO rather using another syntax ?? NOT FOR NOW rather should first add OR syntax
		        // (i.e. autojoins ?!) to OR-like 'equals' on list elements, or prevent them)))
		        // parsing using the mongodb syntax (so no sort) :
				break;
				
			case EQUALS:
				queryParsingContext.getCriteria().and(entityFieldPath).is(parsedData);
				break;
			
			case EXISTS:
				// TODO TODO rather hasAspect / mixin / type !!!!!!!!!!!!!!!
				// TODO AND / OR field value == null
				// TODO sparse index ?????????
				// TODO TODO can't return false because already failed to find field
				queryParsingContext.getCriteria().and(entityFieldPath).exists((boolean)parsedData); // TODO same fieldPath for mongodb ??
				break;
			
			case GREATER_OR_EQUAL:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).gte(parsedData);
				break;
			
			case GREATER_THAN:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).gt(parsedData); // TODO same fieldPath for mongodb ??
				break;
			
			case IN:
				// TODO check that not date ???
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).in(parsedData); // TODO same fieldPath for mongodb ??
				break;
		
			case LOWER_OR_EQUAL:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??<commit_msg>fix query parsing service<commit_after>package org.oasis.datacore.rest.server.parsing.service.impl;

import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;

import org.oasis.datacore.core.meta.model.DCField;
import org.oasis.datacore.core.meta.model.DCFieldTypeEnum;
import org.oasis.datacore.core.meta.model.DCListField;
import org.oasis.datacore.rest.server.parsing.exception.ResourceParsingException;
import org.oasis.datacore.rest.server.parsing.model.DCQueryParsingContext;
import org.oasis.datacore.rest.server.parsing.model.QueryOperatorsEnum;
import org.oasis.datacore.rest.server.parsing.service.QueryParsingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.QueryTimeoutException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;

@Service
public class QueryParsingServiceImpl implements QueryParsingService {

	@Autowired
	@Qualifier("datacoreApiServer.objectMapper")
	public ObjectMapper mapper;
	
	public void parseCriteriaFromQueryParameter(String fieldPath, String operatorAndValue, 
												DCField dcField, DCQueryParsingContext queryParsingContext) 
												throws ResourceParsingException {
		// TODO (mongo)operator for error & in parse ?
		String entityFieldPath = "_p." + fieldPath;
		
		// QueryOperatorEnum = operator name
		// Integer = operator size
		SimpleEntry<QueryOperatorsEnum, Integer> operatorEntry = QueryOperatorsEnum.getEnumFromOperator(operatorAndValue);
		QueryOperatorsEnum operatorEnum = operatorEntry.getKey();
		int operatorSize = operatorEntry.getValue();
		// We check if the selected operator is suitable for the type of DCField
		isQueryOperatorSuitableForField(dcField, operatorEnum);
		// If the operator is a sort (e.g. name=-) there is no value !
		// So we don't need to substring to remove the operator from the value as there is no value
		String queryValue = null;
		QueryOperatorsEnum sortEnum = null;
		Object parsedData = null;
		if (operatorEnum != null) {
			if (!QueryOperatorsEnum.SORT_ASC.name().equals(operatorEnum.name()) 
			&& !QueryOperatorsEnum.SORT_DESC.name().equals(operatorEnum.name())
			&& !QueryOperatorsEnum.EXISTS.name().equals(operatorEnum.name())) {
				// We get the value of the selected operator
				queryValue = operatorAndValue.substring(operatorSize);
				// We need to know if we need to sort the value
				sortEnum = isSortNeeded(queryValue);
				// If field is sorted we need to remove the last char (+ or -)
				if(sortEnum != null) {
					queryValue = queryValue.substring(0, queryValue.length()-1);
				}
				// We get the DCFieldType enum according to the type of the field
				DCFieldTypeEnum dcFieldTypeEnum = DCFieldTypeEnum.getEnumFromStringType(dcField.getType());
				if(dcFieldTypeEnum == null) {
					throw new ResourceParsingException("can't find the type of " + dcField.getName());
				}
				if(QueryOperatorsEnum.SIZE.name().equals(operatorEnum.name())) {
					dcFieldTypeEnum = DCFieldTypeEnum.INTEGER;
				}
				// Then we parse the data
				parsedData = parseValue(dcFieldTypeEnum, queryValue);
				if(parsedData == null) {
					throw new ResourceParsingException("Field " + dcField.getName() + " cannot be parsed in " + dcFieldTypeEnum.getType() + " format");
				}
			}
		} else {
			throw new ResourceParsingException("Query operator wasn't identified for query parameter " + operatorAndValue);
		}
		switch(operatorEnum) {
		
			case ALL: 
				// TODO LATER $all with $elemMatch
				// TODO same fieldPath for mongodb ??
				if(parsedData instanceof ArrayList<?>) {
					queryParsingContext.getCriteria().and(entityFieldPath).all((Collection<?>)parsedData);
				}
				break;
				
			case ELEM_MATCH:
				if (!"list".equals(dcField.getType())) {
					throw new ResourceParsingException("$elemMatch can only be applied to a list but found " + dcField.getType() + " Field");
				}
				// parsing using the latest upmost list field :
				// WARNING the first element in the array must be selective, because all documents
				// containing it are scanned
		        // TODO which syntax ??
		        // TODO alt 1 : don't support it as a different operator but auto use it on list fields
		        // (save if ex. boolean mode...) ; and add OR syntax here using ex. &join=OR syntax NO WRONG 
		        // alt 2 : support it explicitly, with pure mongo syntax NO NOT IN HTTP GET QUERY
		        // alt 3 : support it explicitly, with Datacore query syntax MAYBE ex. :
		        
		        // (((TODO rather using another syntax ?? NOT FOR NOW rather should first add OR syntax
		        // (i.e. autojoins ?!) to OR-like 'equals' on list elements, or prevent them)))
		        // parsing using the mongodb syntax (so no sort) :
				break;
				
			case EQUALS:
				queryParsingContext.getCriteria().and(entityFieldPath).is(parsedData);
				break;
			
			case EXISTS:
				// TODO TODO rather hasAspect / mixin / type !!!!!!!!!!!!!!!
				// TODO AND / OR field value == null
				// TODO sparse index ?????????
				// TODO TODO can't return false because already failed to find field
				queryParsingContext.getCriteria().and(entityFieldPath).exists(true); // TODO same fieldPath for mongodb ??
				break;
			
			case GREATER_OR_EQUAL:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).gte(parsedData);
				break;
			
			case GREATER_THAN:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).gt(parsedData); // TODO same fieldPath for mongodb ??
				break;
			
			case IN:
				// TODO check that not date ???
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
				addSort(entityFieldPath, sortEnum, queryParsingContext);
				queryParsingContext.getCriteria().and(entityFieldPath).in(parsedData); // TODO same fieldPath for mongodb ??
				break;
		
			case LOWER_OR_EQUAL:
				// TODO LATER allow (joined) resource and order per its default order field ??
				// TODO check that not i18n (which is map ! ; or use locale or allow fallback) ???
				// TODO check that indexed (or set low limit) ??
{'fraction_non_alphanumeric': 0.09911504424778761, 'fraction_numerical': 0.0019174041297935103, 'mean_word_length': 3.6125850340136054, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3443027', 'n_tokens_mistral': 4460, 'n_tokens_neox': 3789, 'n_words': 1204}