/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.xcmis.search.query.content;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.NotImplementedException;
import org.junit.After;
import org.junit.Before;
import org.xcmis.search.SearchService;
import org.xcmis.search.config.IndexConfiguration;
import org.xcmis.search.config.SearchServiceConfiguration;
import org.xcmis.search.content.ContentEntry;
import org.xcmis.search.content.InMemorySchema;
import org.xcmis.search.content.IndexModificationException;
import org.xcmis.search.content.Property;
import org.xcmis.search.content.Schema;
import org.xcmis.search.content.InMemorySchema.Builder;
import org.xcmis.search.content.Property.SimpleValue;
import org.xcmis.search.content.interceptors.ContentReaderInterceptor;
import org.xcmis.search.lucene.content.SchemaTableResolver;
import org.xcmis.search.model.Query;
import org.xcmis.search.model.constraint.Operator;
import org.xcmis.search.query.QueryBuilder;
import org.xcmis.search.result.ScoredRow;
import org.xcmis.search.value.CastSystem;
import org.xcmis.search.value.NameConverter;
import org.xcmis.search.value.PropertyType;
import org.xcmis.search.value.ToStringNameConverter;
import org.xcmis.spi.utils.Logger;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Abstract base class for query test cases.
*/
public abstract class AbstractQueryTest
{
/** Logger. */
private static final Logger LOG = Logger.getLogger(AbstractQueryTest.class);
/**
* Resolved Name for jcr:score
*/
protected String jcrScore;
/**
* Resolved Name for jcr:path
*/
protected String jcrPath;
/**
* Resolved Name for jcr:root
*/
protected String jcrRoot;
/**
* Resolved Name for jcr:contains
*/
protected String jcrContains;
/**
* Resolved Name for jcr:deref
*/
protected String jcrDeref;
/**
* The string /${jcrRoot}${testRoot} with all components of the test path
* properly escaped for XPath.
*
* @see <a href="https://issues.apache.org/jira/browse/JCR-714">JCR-714</a>
*/
protected String xpathRoot;
/**
* The query object model factory for {@link #superuser}.
*/
protected QueryBuilder qf;
/**
* The query manager for {@link #superuser}
*/
protected SearchService qm;
protected Node testRootNode;
protected String rootNodeType = "rootNodeType";
protected String testNodeType = "testNodeType";
protected String testNodeType2 = "testNodeType2";
protected String nodeName1 = "nodeName1";
protected String nodeName2 = "nodeName2";
protected String nodeName3 = "nodeName3";
protected String propertyName1 = "propertyName1";
protected String propertyName2 = "propertyName2";
protected String propertyName4 = "propertyName4";
private File tempDir;
private Schema schema;
protected SearchService searchService;
private String testRoot;
/**
* Set-up the configuration values used for the test. Per default retrieves a
* session, configures testRoot, and nodetype and checks if the query
* language for the current language is available.<br>
*/
@Before
public void setUp() throws Exception
{
tempDir = new File(System.getProperty("java.io.tmpdir"), "search-service");
if (tempDir.exists())
{
assertThat(FileUtils.deleteQuietly(tempDir), is(true));
}
assertThat(tempDir.mkdirs(), is(true));
Builder schemaBuilder = InMemorySchema.createBuilder();
schema =
schemaBuilder.addTable(rootNodeType, propertyName1, propertyName2).addTable(testNodeType,
new String[]{propertyName4})
.addColumn(testNodeType, propertyName1, PropertyType.STRING, true, Operator.ALL).addColumn(testNodeType,
propertyName2, PropertyType.STRING, true, Operator.ALL).addTable(testNodeType2, propertyName4).build();
qf = new QueryBuilder(mock(CastSystem.class));
testRootNode =
new Node(null, "", new String[]{rootNodeType}, UUID.randomUUID().toString(),
new String[]{Node.ROOT_PARENT_UUID}, new Property[0]);
//testRoot = testRootNode.getPath();
//value
NameConverter<String> nameConverter = new ToStringNameConverter();
SchemaTableResolver tableResolver = new SchemaTableResolver(nameConverter, schema);
//index configuration
IndexConfiguration indexConfuration =
new IndexConfiguration(testRootNode.getParentIdentifiers()[0], testRootNode.getIdentifier());
//search service configuration
SearchServiceConfiguration configuration =
new SearchServiceConfiguration(schema, tableResolver, mock(ContentReaderInterceptor.class), indexConfuration);
searchService = new SearchService(configuration);
searchService.start();
List<ContentEntry> contentEntries = new ArrayList<ContentEntry>();
contentEntries.add(testRootNode);
searchService.update(contentEntries, Collections.EMPTY_SET);
}
@After
public void tearDown() throws Exception
{
Set<String> removed = new HashSet<String>();
//removed.add(testRootNode.getIdentifier());
for (ContentEntry entry : testRootNode.getTree())
{
removed.add(entry.getIdentifier());
}
searchService.update(Collections.EMPTY_LIST, removed);
qm = null;
qf = null;
}
/**
* Create a {@link Query} for a given {@link Statement}.
*
* @param statement
* the query should be created for
* @return
*
* @throws RepositoryException
* @see #createQuery(String, String)
*/
protected Query createQuery(String statement)
{
throw new NotImplementedException();
}
/**
* Creates a {@link Query} for the given statement in the requested language,
* treating optional languages gracefully
*
* @throws RepositoryException
*/
protected Query createQuery(String statement, String language)
{
throw new NotImplementedException();
}
/**
* Creates and executes a {@link Query} for the given {@link Statement}
*
* @param statement
* to execute
* @return
*
* @throws RepositoryException
* @see #execute(String, String)
*/
protected void execute(String statement)
{
throw new NotImplementedException();
}
/**
* Creates and executes a {@link Query} for a given Statement in a given
* query language
*
* @param statement
* the query should be build for
* @param language
* query language the stement is written in
* @return
*
* @throws RepositoryException
*/
protected void execute(String statement, String language)
{
throw new NotImplementedException();
}
/**
* Checks if the <code>result</code> contains a number of <code>hits</code>.
*
* @param result
* the <code>QueryResult</code>.
* @param hits
* the number of expected hits.
* @throws RepositoryException
* if an error occurs while iterating over the result nodes.
*/
protected void checkResult(List<ScoredRow> result, int hits)
{
long count = result.size();
if (count == 0)
{
LOG.info(" NONE");
}
else if (count == -1)
{
// have to count in a loop
count = 0;
for (ScoredRow scoredRow : result)
{
count++;
}
}
assertEquals("Wrong hit count.", hits, count);
}
/**
* Checks if the <code>result</code> contains a number of <code>hits</code>
* and <code>properties</code>.
*
* @param result
* the <code>QueryResult</code>.
* @param hits
* the number of expected hits.
* @param properties
* the number of expected properties.
* @throws RepositoryException
* if an error occurs while iterating over the result nodes.
*/
protected void checkResult(List<ScoredRow> result, int hits, int properties)
{
// checkResult(result, hits);
// // now check property count
// int count = 0;
// LOG.info("Properties:");
// String[] propNames = result.getColumnNames();
// for (RowIterator it = result.getRows(); it.hasNext();)
// {
// StringBuffer msg = new StringBuffer();
// Value[] values = it.nextRow().getValues();
// for (int i = 0; i < propNames.length; i++, count++)
// {
// msg.append(" ").append(propNames[i]).append(": ");
// if (values[i] == null)
// {
// msg.append("null");
// }
// else
// {
// msg.append(values[i].getString());
// }
// }
// LOG.info(msg);
// }
// if (count == 0)
// {
// LOG.info(" NONE");
// }
// assertEquals("Wrong property count.", properties, count);
throw new NotImplementedException();
}
/**
* Checks if the {@link QueryResult} is ordered according order property in
* direction of related argument.
*
* @param queryResult
* to be tested
* @param propName
* Name of the porperty to order by
* @param descending
* if <code>true</code> order has to be descending
* @throws RepositoryException
* @throws NotExecutableException
* in case of less than two results or all results have same size
* of value in its order-property
*/
protected void evaluateResultOrder(List<ScoredRow> result, String propName, boolean descending)
{
// NodeIterator nodes = queryResult.getNodes();
// if (getSize(nodes) < 2)
// {
// fail("Workspace does not contain sufficient content to test ordering on result nodes.");
// }
// // need to re-aquire nodes, {@link #getSize} may consume elements.
// nodes = queryResult.getNodes();
// int changeCnt = 0;
// String last = descending ? "\uFFFF" : "";
// while (nodes.hasNext())
// {
// String value = nodes.nextNode().getProperty(propName).getString();
// int cp = value.compareTo(last);
// // if value changed evaluate if the ordering is correct
// if (cp != 0)
// {
// changeCnt++;
// if (cp > 0 && descending)
// {
// fail("Repository doesn't order properly descending");
// }
// else if (cp < 0 && !descending)
// {
// fail("Repository doesn't order properly ascending");
// }
// }
// last = value;
// }
// if (changeCnt < 1)
// {
// fail("Workspace does not contain distinct values for " + propName);
// }
throw new NotImplementedException();
}
/**
* Executes the <code>sql</code> query and checks the results against the
* specified <code>nodes</code>.
*
* @param session
* the session to use for the query.
* @param sql
* the sql query.
* @param nodes
* the expected result nodes.
* @throws NotExecutableException
*/
protected void executeSqlQuery(String sql, String[] nodes)
{
throw new NotImplementedException();
}
protected void save(Node node) throws IndexModificationException
{
searchService.update(node.getTree(), Collections.EMPTY_SET);
}
/**
* Checks if the result set contains exactly the <code>nodes</code>.
*
* @param result
* the query result.
* @param nodes
* the expected nodes in the result set.
*/
protected void checkResult(List<ScoredRow> result, String selectorName, Node[] nodes)
{
// collect paths
Set<String> expectedPaths = new HashSet<String>();
for (Node node : nodes)
{
expectedPaths.add(node.getIdentifier());
}
Set<String> resultPaths = new HashSet<String>();
for (ScoredRow object : result)
{
resultPaths.add(object.getNodeIdentifer(selectorName));
}
// check if all expected are in result
for (Object element : expectedPaths)
{
String path = (String)element;
assertTrue(path + " is not part of the result set", resultPaths.contains(path));
}
// check result does not contain more than expected
for (Object element : resultPaths)
{
String path = (String)element;
assertTrue(path + " is not expected to be part of the result set", expectedPaths.contains(path));
}
//throw new NotImplementedException();
}
// /**
// * Returns the nodes in <code>it</code> as an array of Nodes.
// * @param it the NodeIterator.
// * @return the elements of the iterator as an array of Nodes.
// */
// protected String[] toArray(NodeIterator it)
// {
// List nodes = new ArrayList();
// while (it.hasNext())
// {
// nodes.add(it.nextNode());
// }
// return (Node[])nodes.toArray(new Node[nodes.size()]);
// }
/**
* Escape an identifier suitable for the SQL parser
*
* @TODO currently only handles dash character
*/
protected String escapeIdentifierForSQL(String identifier)
{
boolean needsEscaping = identifier.indexOf('-') >= 0;
if (!needsEscaping)
{
return identifier;
}
else
{
return '"' + identifier + '"';
}
}
// /**
// * @param language a query language.
// * @return <code>true</code> if <code>language</code> is supported;
// * <code>false</code> otherwise.
// * @throws RepositoryException if an error occurs.
// */
// protected boolean isSupportedLanguage(String language) throws RepositoryException
// {
// return Arrays.asList(qm.getSupportedQueryLanguages()).contains(language);
// }
public static class Node extends ContentEntry
{
public static final String ROOT_PARENT_UUID = "";
private final List<Node> childNodes;
private final Map<String, Property<?>> properties;
private final Node parentNode;
/**
* @param name
* @param tableNames
* @param identifer
* @param parentIdentifiers
* @param properties
*/
public Node(Node parentNode, String name, String[] tableNames, String identifer, String[] parentIdentifiers,
Property<?>[] properties)
{
super(name, tableNames, identifer, parentIdentifiers, properties);
this.parentNode = parentNode;
this.childNodes = new ArrayList<Node>();
this.properties = new HashMap<String, Property<?>>();
}
/**
* @param nodeName1
* @param testNodeType
* @return
*/
public Node addNode(String nodeName, String testNodeType)
{
Node child =
new Node(this, nodeName, new String[]{testNodeType}, UUID.randomUUID().toString(),
new String[]{getIdentifier()}, new Property[0]);
childNodes.add(child);
return child;
}
/**
* @param propertyName1
* @param string
*/
public void setProperty(String propertyName1, String string)
{
properties.put(propertyName1, new Property<String>(PropertyType.STRING, propertyName1,
new SimpleValue<String>(string)));
}
/**
* @see org.xcmis.search.content.ContentEntry#getProperties()
*/
@Override
public Property<?>[] getProperties()
{
return properties.values().toArray(new Property[properties.size()]);
}
/**
* @return
*/
public String getPath()
{
if (parentNode == null)
{
return "/";
}
return parentNode.getPath() + "/" + getName();
}
public List<ContentEntry> getTree()
{
List<ContentEntry> nodes = new ArrayList<ContentEntry>();
nodes.add(this);
for (Node childNode : childNodes)
{
nodes.addAll(childNode.getTree());
}
return nodes;
}
}
}