/* * 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; } } }