Commit 3ecbf006 authored by Brian Long's avatar Brian Long
Browse files

added query parsing (incomplete)

parent 07d6e634
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@

	<properties>
		<alfresco.sdk.version>5.2.0</alfresco.sdk.version>
		<alfresco.platform.version>23.3.0</alfresco.platform.version>
		<alfresco.platform.version>7.0.0</alfresco.platform.version>
	</properties>

	<dependencyManagement>
+67 −0
Original line number Diff line number Diff line
package com.inteligr8.alfresco.asie.cache;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;

import org.alfresco.repo.cache.SimpleCache;

public class MultiValueCache<K extends Serializable, V, C extends Collection<V>> implements SimpleCache<K, C> {
	
	private SimpleCache<K, C> cache;
	private Class<?> collectionType;
	
	public MultiValueCache(SimpleCache<K, C> cache, Class<?> collectionType) {
		this.cache = cache;
		this.collectionType = collectionType;
	}
	
	@SuppressWarnings("unchecked")
	public boolean add(K key, V value) {
		C c = this.cache.get(key);
		if (c != null)
			return c.add(value);
		
		try {
			Constructor<?> constructor = this.collectionType.getConstructor();
			c = (C) constructor.newInstance();
			this.cache.put(key, c);
			return c.add(value);
		} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
			throw new UnsupportedOperationException(e.getMessage(), e);
		}
	}
	
	@Override
	public void clear() {
		this.cache.clear();
	}
	
	@Override
	public C get(K key) {
		return this.cache.get(key);
	}
	
	@Override
	public boolean contains(K key) {
		C c = this.cache.get(key);
		return c == null ? false : !c.isEmpty();
	}
	
	@Override
	public Collection<K> getKeys() {
		return this.cache.getKeys();
	}
	
	@Override
	public void put(K key, C value) {
		this.cache.put(key, value);
	}
	
	@Override
	public void remove(K key) {
		this.cache.remove(key);
	}

}
+47 −0
Original line number Diff line number Diff line
package com.inteligr8.alfresco.asie.compute;

import java.util.Set;

import org.alfresco.repo.search.impl.parsers.CMISLexer;
import org.alfresco.repo.search.impl.parsers.CMISParser;
import org.alfresco.service.cmr.search.SearchParameters.Operator;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.QName;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.Tree;
import org.apache.commons.collections4.SetUtils;
import org.springframework.stereotype.Component;

@Component
public class CmisQueryInspector implements QueryInspector {
	
	private Set<String> supportedLanguages = SetUtils.unmodifiableSet(
			SearchService.LANGUAGE_CMIS_ALFRESCO,
			SearchService.LANGUAGE_CMIS_STRICT,
			SearchService.LANGUAGE_INDEX_CMIS,
			SearchService.LANGUAGE_SOLR_CMIS);
	
	@Override
	public Set<String> getSupportedLanguages() {
		return this.supportedLanguages;
	}
	
	@Override
	public QueryValue findRequiredProperty(String cmisQuery, Operator defaultOperator, QName property) throws RecognitionException {
		Tree tree = this.parseCmis(cmisQuery, defaultOperator);
	}
	
	protected Tree parseCmis(String cmisQuery, Operator defaultOperator) throws RecognitionException {
	    CharStream cs = new ANTLRStringStream(cmisQuery);
	    CMISLexer lexer = new CMISLexer(cs);
	    CommonTokenStream tokens = new CommonTokenStream(lexer);
	    CMISParser parser = new CMISParser(tokens);
	    CommonTree tree = (CommonTree) parser.query().getTree();
	    return tree;
	}

}
+290 −0
Original line number Diff line number Diff line
package com.inteligr8.alfresco.asie.compute;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.alfresco.repo.search.impl.parsers.FTSLexer;
import org.alfresco.repo.search.impl.parsers.FTSParser;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.SearchParameters.Operator;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.Tree;
import org.apache.commons.collections4.SetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class FtsQueryInspector implements QueryInspector {
	
	private final Logger logger = LoggerFactory.getLogger(FtsQueryInspector.class);
	
	private final Set<String> supportedLanguages = SetUtils.unmodifiableSet(
			SearchService.LANGUAGE_FTS_ALFRESCO,
			SearchService.LANGUAGE_INDEX_FTS_ALFRESCO,
			SearchService.LANGUAGE_SOLR_FTS_ALFRESCO,
			SearchService.LANGUAGE_LUCENE);
	
	@Autowired
	private NamespaceService namespaceService;
	
	@Override
	public Set<String> getSupportedLanguages() {
		return this.supportedLanguages;
	}
	
	@Override
	public List<QueryValue> findRequiredPropertyValues(String ftsQuery, Operator defaultOperator, QName property, DataTypeDefinition dataTypeDef) throws RecognitionException {
		Tree tree = this.parseFts(ftsQuery, defaultOperator);
		tree = this.bypassSingleTermDisjunctions(tree);
		if (tree == null)
			return null;
		
		Collection<Tree> trees = this.extractRequiredTerms(tree);
		this.logger.trace("Found {} required terms in query: {}", trees.size(), ftsQuery);
		this.filterPropertyTerms(trees, property);
		this.logger.trace("Found {} required terms for property {} in query: {}", trees.size(), property, ftsQuery);
		this.filterOutFuzzyTerms(trees);
		this.logger.trace("Found {} required definitive terms for property {} in query: {}", trees.size(), property, ftsQuery);

		List<QueryValue> values = new ArrayList<>(trees.size());
		for (Tree t : trees)
			values.add(this.extractValue(t, dataTypeDef));
		return values;
	}
	
	protected Tree parseFts(String ftsQuery, Operator defaultOperator) throws RecognitionException {
	    CharStream cs = new ANTLRStringStream(ftsQuery);
	    FTSLexer lexer = new FTSLexer(cs);
	    CommonTokenStream tokens = new CommonTokenStream(lexer);
	    FTSParser parser = new FTSParser(tokens);
	    parser.setDefaultFieldConjunction(defaultOperator.equals(Operator.AND));
	    parser.setMode(defaultOperator.equals(Operator.AND) ? FTSParser.Mode.DEFAULT_CONJUNCTION : FTSParser.Mode.DEFAULT_DISJUNCTION);
	    CommonTree tree = (CommonTree) parser.ftsQuery().getTree();
	    return tree;
	}
	
	protected Tree bypassSingleTermDisjunctions(Tree tree) {
		while ("DISJUNCTION".equals(tree.getText()) && tree.getChildCount() == 1)
			tree = tree.getChild(0);
		if ("DISJUNCTION".equals(tree.getText()))
			return null;
		return tree;
	}
	
	protected Collection<Tree> extractRequiredTerms(Tree tree) {
		while ("DISJUNCTION".equals(tree.getText()) && tree.getChildCount() == 1)
			tree = tree.getChild(0);

		List<Tree> terms = new LinkedList<>();
		
		switch (tree.getText()) {
			case "DISJUNCTION":
				break;
			case "CONJUNCTION":
				for (int c = 0; c < tree.getChildCount(); c++) {
					Collection<Tree> subtrees = this.extractRequiredTerms(tree.getChild(c));
					if (subtrees == null || subtrees.isEmpty())
						continue;
					terms.addAll(subtrees);
				}
				break;
			case "DEFAULT":
				terms.add(tree);
				break;
			default:
				this.logger.warn("Unexpected/unsupported tree: {}", tree.getText());
		}
		
		return terms;
	}
	
	protected Collection<Tree> filterPropertyTerms(Collection<Tree> trees, QName property) {
		if (trees.isEmpty())
			return trees;
		
		Set<String> prefixes = new HashSet<>(this.namespaceService.getPrefixes(property.getNamespaceURI()));
		if (prefixes.isEmpty()) {
			this.logger.warn("Unexpected/unsupported namespace: {}", property.getNamespaceURI());
			trees.clear();
			return trees;
		}
		
		Iterator<Tree> i = trees.iterator();
		
		while (i.hasNext()) {
			Tree tree = i.next();
			
			if ("DEFAULT".equals(tree.getText()))
				tree = tree.getChild(0);
			
			int skip = -1;
			switch (tree.getText()) {
				case "TERM":
				case "PHRASE":
				case "EXACT_TERM":
				case "EXACT_PHRASE":
					skip = 1; // skip the value child
					break;
				case "RANGE":
					skip = 4; // skip the inclusive, start, end, inclusive children
					break;
				default:
			}
			
			if (skip >= 0) {
				Tree fieldRef = tree.getChild(skip);
				if (!"FIELD_REF".equals(fieldRef.getText())) {
					this.logger.warn("Unexpected/unsupported tree: {}", tree.getText());
				} else if (!fieldRef.getChild(0).getText().equals(property.getLocalName())) {
					this.logger.trace("Found but ignoring property: {}", fieldRef.getChild(0).getText());
				} else {
					Tree prefix = fieldRef.getChild(1);
					if (!"PREFIX".equals(prefix.getText())) {
						this.logger.warn("Unexpected/unsupported tree: {}", tree.getText());
					} else if (!prefixes.contains(prefix.getChild(0).getText())) {
						this.logger.trace("Found but ignoring property: {}:{}", prefix.getChild(0).getText(), property.getLocalName());
					} else {
						// this will skip the remove()
						continue;
					}
				}
			}

			i.remove();
		}
		
		return trees;
	}
	
	protected Collection<Tree> filterOutFuzzyTerms(Collection<Tree> trees) {
		if (trees.isEmpty())
			return trees;
		
		Iterator<Tree> i = trees.iterator();
		
		while (i.hasNext()) {
			Tree tree = i.next();
			
			if ("DEFAULT".equals(tree.getText()))
				tree = tree.getChild(0);
			
			switch (tree.getText()) {
				case "EXACT_TERM":
				case "EXACT_PHRASE":
				case "RANGE":
					break;
				default:
					i.remove();
			}
		}
		
		return trees;
	}
	
	protected QueryValue extractValue(Tree tree, DataTypeDefinition dataTypeDef) {
		if ("DEFAULT".equals(tree.getText()))
			tree = tree.getChild(0);

		switch (tree.getText()) {
			case "RANGE":
				return this.extractRangeValue(tree, dataTypeDef);
			default:
		}

		String value = this.unquote(tree.getChild(0).getText());

		switch (dataTypeDef.getName().getLocalName()) {
			case "boolean":
				return new QuerySingleValue<Boolean>(Boolean.parseBoolean(value));
			case "double":
				return new QuerySingleValue<Double>(Double.parseDouble(value));
			case "float":
				return new QuerySingleValue<Float>(Float.parseFloat(value));
			case "int":
				return new QuerySingleValue<Integer>(Integer.parseInt(value));
			case "long":
				return new QuerySingleValue<Long>(Long.parseLong(value));
			case "date":
				return new QuerySingleValue<LocalDate>(this.evaluateAsDate(value));
			case "datetime":
				return new QuerySingleValue<LocalDateTime>(this.evaluateAsDateTime(value));
			case "period":
				return new QuerySingleValue<Period>(Period.parse(value));
			case "qname":
				return new QuerySingleValue<QName>(QName.createQName(value, this.namespaceService));
			case "noderef":
				return new QuerySingleValue<NodeRef>(new NodeRef(value));
			case "childassocref":
				return new QuerySingleValue<ChildAssociationRef>(new ChildAssociationRef(value));
			case "assocref":
				return new QuerySingleValue<AssociationRef>(new AssociationRef(value));
			case "locale":
				return new QuerySingleValue<Locale>(new Locale(value));
			default:
				return new QuerySingleValue<String>(value);
		}
	}
	
	protected QueryRangeValue<?> extractRangeValue(Tree tree, DataTypeDefinition dataTypeDef) {
		boolean includeStart = "INCLUSIVE".equals(tree.getChild(0).getText());
		String start = this.unquote(tree.getChild(1).getText());
		String end = this.unquote(tree.getChild(2).getText());
		boolean includeEnd = "INCLUSIVE".equals(tree.getChild(3).getText());
		
		switch (dataTypeDef.getName().getLocalName()) {
			case "double":
				return new QueryRangeValue<Double>(includeStart, Double.parseDouble(start), includeEnd, Double.parseDouble(end));
			case "float":
				return new QueryRangeValue<Float>(includeStart, Float.parseFloat(start), includeEnd, Float.parseFloat(end));
			case "int":
				return new QueryRangeValue<Integer>(includeStart, Integer.parseInt(start), includeEnd, Integer.parseInt(end));
			case "long":
				return new QueryRangeValue<Long>(includeStart, Long.parseLong(start), includeEnd, Long.parseLong(end));
			case "date":
				return new QueryRangeValue<LocalDate>(includeStart, this.evaluateAsDate(start), includeEnd, this.evaluateAsDate(end));
			case "datetime":
				return new QueryRangeValue<LocalDateTime>(includeStart, this.evaluateAsDateTime(start), includeEnd, this.evaluateAsDateTime(end));
			default:
				throw new UnsupportedOperationException("The data type does not make sense for range evaluation: " + dataTypeDef.getName());
		}
	}
	
	protected LocalDate evaluateAsDate(String str) {
		if ("now".equalsIgnoreCase(str)) return LocalDate.now();
		else return LocalDate.parse(str);
	}
	
	protected LocalDateTime evaluateAsDateTime(String str) {
		if ("now".equalsIgnoreCase(str)) return LocalDateTime.now();
		else return LocalDateTime.parse(str);
	}
	
	protected String unquote(String str) {
		if (str.length() < 2) return str;
		else if (str.charAt(0) == '\'' && str.charAt(str.length()-1) == '\'') return str.substring(1, str.length()-1);
		else if (str.charAt(0) == '\"' && str.charAt(str.length()-1) == '\"') return str.substring(1, str.length()-1);
		else return str;
	}

}
+74 −0
Original line number Diff line number Diff line
package com.inteligr8.alfresco.asie.compute;

import java.util.List;
import java.util.Set;

import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.search.SearchParameters.Operator;
import org.alfresco.service.namespace.QName;
import org.antlr.runtime.RecognitionException;

public interface QueryInspector {
	
	Set<String> getSupportedLanguages();
	
	List<QueryValue> findRequiredPropertyValues(String query, Operator defaultOperator, QName property, DataTypeDefinition dataTypeDef) throws RecognitionException;
	
	
	
	public interface QueryValue {
		
	}
	
	public class QuerySingleValue<T> implements QueryValue {
		
		private T value;
		
		public QuerySingleValue(T value) {
			this.value = value;
		}
		
		public T getValue() {
			return value;
		}
		
		@Override
		public String toString() {
			return this.value.toString();
		}
		
	}
	
	public class QueryRangeValue<T> implements QueryValue {
		
		private boolean includeStart;
		private T start;
		private boolean includeEnd;
		private T end;
		
		public QueryRangeValue(boolean includeStart, T start, boolean includeEnd, T end) {
			this.includeStart = includeStart;
			this.start = start;
			this.includeEnd = includeEnd;
			this.end = end;
		}
		
		public boolean isIncludeStart() {
			return includeStart;
		}
		
		public boolean isIncludeEnd() {
			return includeEnd;
		}
		
		public T getStart() {
			return start;
		}
		
		public T getEnd() {
			return end;
		}
		
	}

}
Loading