com.flexive.tests.embedded.cmis.CmisSearchEngineTest.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.tests.embedded.cmis.CmisSearchEngineTest.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.tests.embedded.cmis;

import com.flexive.shared.CacheAdmin;
import com.flexive.shared.EJBLookup;
import com.flexive.shared.FxContext;
import com.flexive.shared.FxSharedUtils;
import com.flexive.shared.cmis.CmisVirtualProperty;
import com.flexive.shared.cmis.search.CmisResultRow;
import com.flexive.shared.cmis.search.CmisResultSet;
import com.flexive.shared.content.FxContent;
import com.flexive.shared.content.FxPK;
import com.flexive.shared.exceptions.*;
import com.flexive.shared.interfaces.ContentEngine;
import com.flexive.shared.structure.*;
import com.flexive.shared.tree.FxTreeMode;
import com.flexive.shared.tree.FxTreeNode;
import com.flexive.shared.tree.FxTreeNodeEdit;
import com.flexive.shared.tree.FxTreeRemoveOp;
import com.flexive.shared.value.*;
import com.flexive.tests.embedded.FxTestUtils;
import com.flexive.tests.embedded.TestUsers;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

import java.io.ByteArrayInputStream;
import java.util.*;

import static com.flexive.shared.EJBLookup.*;
import static org.testng.Assert.*;

/**
 * Tests for the CMIS search engine.
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 * @version $Rev$
 * @since 3.1
 */
public class CmisSearchEngineTest {
    private final List<FxPK> testInstances = new ArrayList<FxPK>();

    @BeforeClass(groups = { "search", "cmis", "ejb" })
    public void init() throws FxLoginFailedException, FxAccountInUseException, FxApplicationException {
        FxTestUtils.login(TestUsers.REGULAR);
        createContactData("nestedCondition", "First");
        createContactData("nestedCondition", "Second");
        createContactData("nestedCondition", "Third");
        createArticle("About Maven",
                "Apache Maven is a build tool like Apache Ant, but with a completely different "
                        + "approach to managing software builds: where Apache Ant is like a procedural programming language "
                        + "for describing how  to build your system, Apache Maven is more of a fully-fledged assembly line "
                        + "for building, packaging, testing, and running software projects. ");
        createArticle("Archetypes",
                " Maven archetypes provide a quick start for new projects. Currently we offer a "
                        + "archetype for a full enterprise application with web and console frontends. Since the archetype "
                        + "includes setup scripts for the H2 database, it can be used without external dependencies like "
                        + "MySQL. However, for administration tasks such as database setup you currently have to use the "
                        + "tools provided by the [fleXive] distribution. ");
    }

    @AfterClass(groups = { "search", "cmis", "ejb" })
    public void cleanup() throws FxApplicationException, FxLogoutFailedException {
        try {
            FxContext.get().runAsSystem();
            for (FxPK pk : testInstances) {
                getContentEngine().remove(pk);
            }
        } finally {
            FxContext.get().stopRunAsSystem();
        }
        FxTestUtils.logout();
    }

    @Test(groups = { "search", "cmis" })
    public void resultFilter() {
        final CmisResultSet rs = new CmisResultSet(2);
        rs.addRow(rs.newRow().setValue(1, "Apple").setValue(2, new FxNumber(25)));
        rs.addRow(rs.newRow().setValue(1, "Banana").setValue(2, new FxNumber(30)));
        assertEquals(rs.filterEqual(1, "Apple").size(), 1);
        assertEquals(rs.filterEqual(1, "Apple").get(0).getColumn(1).toString(), "Apple");
        assertEquals(rs.filterEqual(2, 30).size(), 1);
        assertEquals(rs.filterEqual(2, 30).get(0).getColumn(2).getValue(), new FxNumber(30));
        assertEquals(rs.filterEqual(2, new FxNumber(30)).get(0).getColumn(2).getValue(), new FxNumber(30));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void simpleSelect() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT d.surname FROM contactData AS d");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            assertTrue(!row.getColumn(1).isEmpty(), "Value should not be empty: " + row.getColumn(1));
            System.out.println(row.getColumn(1));
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void paging() throws FxApplicationException {
        final String query = "SELECT name FROM cmis_person ORDER BY name DESC";
        final CmisResultSet refResult = getCmisSearchEngine().search(query);
        final int expectedRows = 4;
        assertRowCount(refResult, expectedRows);
        assertEquals(refResult.collectColumnValues(1),
                Arrays.asList("Sandra Locke", "Peter Bones", "Martin Black", "Alex Cervais"));

        int startRow = 0;
        final int maxRows = 2;
        while (startRow < expectedRows) {
            final CmisResultSet result = getCmisSearchEngine().search(query, true, startRow, maxRows);
            assertRowCount(result, Math.min(maxRows, expectedRows - startRow));
            for (int idx = startRow; idx < startRow + maxRows; idx++) {
                if (idx < refResult.getRowCount()) {
                    assertEquals(refResult.getColumn(idx, 1).getValue(),
                            result.getColumn(idx - startRow, 1).getValue());
                }
            }
            startRow += maxRows;
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void simpleCondition() throws FxApplicationException {
        createContactData("simpleConditionTest", null);
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT d.surname FROM contactData AS d WHERE d.surname = 'simpleConditionTest'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).toString(), "simpleConditionTest");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void simpleDisjunction() throws FxApplicationException {
        createContactData("simpleDisjunction1", null);
        createContactData("simpleDisjunction2", null);
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT surname FROM contactData WHERE surname = 'simpleDisjunction1' OR surname = 'simpleDisjunction2'");
        assertRowCount(result, 2);
        assertTrue(result.getColumn(0, 1).toString().startsWith("simpleDisjunction"));
        assertTrue(result.getColumn(1, 1).toString().startsWith("simpleDisjunction"));
        assertTrue(!result.getColumn(1, 1).equals(result.getColumn(0, 1)), "Two identical rows returned");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void simpleNegation() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE name <> 'Peter Bones' ORDER BY name");
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Alex Cervais", "Martin Black", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void disjunctionWithOverlap() throws FxApplicationException {
        createContactData("disjunctionWithOverlap", null);
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT surname FROM contactData WHERE surname = 'disjunctionWithOverlap' OR surname = 'disjunctionWithOverlap'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).toString(), "disjunctionWithOverlap");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void nestedCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name, surname FROM contactData WHERE surname = 'nestedCondition' AND (name = 'First' OR name = 'Second')");
        assertRowCount(result, 2);
        assertEquals(result.getColumn(0, 2).toString(), "nestedCondition");
        assertEquals(result.getColumn(1, 2).toString(), "nestedCondition");
        final String row1 = result.getColumn(0, 1).toString();
        final String row2 = result.getColumn(1, 1).toString();
        assertTrue("First".equals(row1) || "First".equals(row2), "Unexpected result: " + row1 + ", " + row2);
        assertTrue("Second".equals(row1) || "Second".equals(row2), "Unexpected result: " + row1 + ", " + row2);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void nestedConditions() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT d.name, d.surname FROM contactData AS d WHERE (surname = 'nestedCondition' AND name='First') "
                        + "  OR ((name = 'Second' OR name = 'Third') AND surname = 'nestedCondition')");
        assertRowCount(result, 3);
        for (String name : new String[] { "First", "Second", "Third" }) {
            boolean found = false;
            for (int i = 0; i < 3; i++) {
                if (name.equals(result.getColumn(i, 1).toString())) {
                    found = true;
                    break;
                }
            }
            assertTrue(found, "Expected to find " + name + " in result.");
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectMainTableColumn() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT d.id, d.surname, d.mandator FROM contactData AS d");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            assertTrue(!row.getColumn(2).isEmpty(), "Column 2 should not be empty: " + row.getColumns());
            final FxContent content = EJBLookup.getContentEngine().load(new FxPK(row.getColumn(1).getLong()));
            assertEquals(row.getColumn(3).getLong(), content.getMandatorId(),
                    "Search result returned invalid mandator ID.");
            assertEquals(row.getColumn(2).toString(), content.getValue("/surname").toString());
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectColumnWithAlias() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT surname AS sn FROM contactData AS d");
        assertTrue(result.getRowCount() > 0);
        assertEquals(result.getColumn(0, "sn"), result.getColumn(0, 1));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectCmisProperties() throws FxApplicationException {
        final List<String> columns = Lists.newArrayList();
        for (CmisVirtualProperty vp : CmisVirtualProperty.values()) {
            if (vp.getFxPropertyName() != null && vp.getFxPropertyName().charAt(0) != '@') {
                // property can be mapped directly, select the CMIS variant and the flexive one
                columns.add(vp.getCmisPropertyName());
                columns.add(vp.getFxPropertyName());
            }
        }
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT " + StringUtils.join(columns, ',') + " FROM cmis:document");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            // check if the document/folder base types filter correctly
            final FxType type = CacheAdmin.getEnvironment()
                    .getType(row.getColumn(CmisVirtualProperty.TypeId.getCmisPropertyName()).getLong());
            assertFalse(type.isDerivedFrom(FxType.FOLDER));

            // check if the CMIS properties select the same value as the FX properties
            for (int i = 0; i < result.getColumnCount() / 2; i++) {
                final int cmis = i * 2 + 1;
                final int flexive = i * 2 + 2;
                assertEquals(row.getColumn(cmis), row.getColumn(flexive),
                        "CMIS property returned other result than flexive property: " + row.getColumn(cmis) + " ("
                                + result.getColumnAliases().get(cmis - 1) + ") vs. " + row.getColumn(flexive) + " ("
                                + result.getColumnAliases().get(flexive - 1) + ")");
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectParent() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT cmis:ObjectId, cmis:ParentId, cmis:Name FROM cmis:folder WHERE cmis:Name NOT LIKE 'test caption%'");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            if (!"root".equalsIgnoreCase(row.getColumn("cmis:name").getString())) {
                final Collection<Long> parentIds = row.getColumn("cmis:ParentId").getValues();
                assertTrue(parentIds.size() > 0);
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectDocumentType() throws FxApplicationException {
        final CmisResultSet allInstances = getCmisSearchEngine().search("SELECT cmis:ObjectId FROM cmis:document");
        // select all content instances
        final CmisResultSet documents = getCmisSearchEngine()
                .search("SELECT cmis:ObjectId FROM " + FxType.DOCUMENT);
        assertTrue(documents.getRowCount() < allInstances.getRowCount(), "Select all returned "
                + allInstances.getRowCount() + " rows, select document returned " + documents.getRowCount());
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectPK() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT " + CmisVirtualProperty.Id.getCmisPropertyName() + ", id, version FROM root");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            assertEquals(row.getColumn(CmisVirtualProperty.Id.getCmisPropertyName()).getValue(),
                    new FxPK(row.getColumn(2).getLong(), row.getColumn(3).getInt()));
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectBinary() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT id, file FROM image");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            final BinaryDescriptor binary = row.getColumn(2).getBinary();
            assertNotNull(binary);
            assertNotNull(binary.getName());
            assertNotNull(binary.getMimeType());
            assertTrue(binary.getSize() > 0, "Expected size to be greater than 0");
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectUpper() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT UPPER(surname) FROM contactData");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            assertTrue(row.getColumn(1).toString().toUpperCase().equals(row.getColumn(1).toString()),
                    "Value should be in upper case: " + row.getColumn(1));
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectLower() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT LOWER(surname) FROM contactData");
        assertTrue(result.getRowCount() > 0);
        for (CmisResultRow row : result) {
            assertTrue(row.getColumn(1).toString().toLowerCase().equals(row.getColumn(1).toString()),
                    "Value should be in lower case: " + row.getColumn(1));
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void stringFunCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT surname FROM contactData WHERE LOWER(surname) = 'nestedcondition' OR UPPER(surname) = 'NESTEDCONDITION'");
        assertTrue(result.getRowCount() > 0, "Lower match did not return any rows");
        for (CmisResultRow row : result) {
            assertEquals(row.getColumn("surname").toString(), "nestedCondition");
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void fulltextQuery() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT id, longtext FROM article WHERE CONTAINS('procedural')");
        assertRowCount(result, 1);
        assertTrue(result.getColumn(0, 2).toString().contains("procedural programming language"),
                "Expected fulltext match against 'complex and evolving': " + result.getColumn(0, 2));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectScore() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT id, longtext, SCORE() AS \"score\" FROM article WHERE CONTAINS('Maven')");
        assertRowCount(result, 2);
        for (CmisResultRow row : result) {
            assertTrue(row.getColumn("longtext").toString().contains("Maven"));
            final double score = row.getColumn("score").getDouble();
            assertTrue(score > 0 && score <= 1, "Score should be between 0 and 1, is: " + score);
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectScoreMulti() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT id, SCORE() AS score1, longtext, SCORE() AS score2 FROM article WHERE CONTAINS('Maven')");
        assertRowCount(result, 2);
        for (CmisResultRow row : result) {
            assertTrue(row.getColumn("longtext").toString().contains("Maven"));
            for (String column : new String[] { "score1", "score2" }) {
                final double score = row.getColumn(column).getDouble();
                assertTrue(score > 0 && score <= 1,
                        "Score in column " + column + " should be between 0 and 1, is: " + score);
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicOrderBy() throws FxApplicationException {
        checkOrderBy("name", "First", "Second", "Third");
        checkOrderBy("name ASC", "First", "Second", "Third");
        checkOrderBy("name DESC", "Third", "Second", "First");
    }

    private void checkOrderBy(String orderBy, Object... expected) throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name, surname FROM contactData WHERE surname='nestedCondition' ORDER BY " + orderBy);
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), Arrays.asList(expected));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void mainTableCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT person.name FROM cmis_person AS person WHERE person.typedef = "
                        + CacheAdmin.getEnvironment().getType("CMIS_PERSON").getId()
                        + " AND person.name = 'Peter Bones'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).getString(), "Peter Bones");

    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void broadTableCondition() throws FxApplicationException {
        // test a condition that is "broader" than the defining type to check if conditions are properly
        // constrained - the can only be triggered as supervisor, because this skips the security
        // (and thus typechecks)
        FxContext.get().runAsSystem();
        final CmisResultSet result;
        try {
            result = getCmisSearchEngine().search("SELECT cmis:ObjectId, typedef FROM cmis_person WHERE step=0");
        } finally {
            FxContext.get().stopRunAsSystem();
        }
        assertTrue(result.getRowCount() > 0);
        final long typeId = CacheAdmin.getEnvironment().getType("cmis_person").getId();
        for (CmisResultRow row : result) {
            assertEquals(row.getColumn(2).getLong(), typeId);
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoin() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) ");
        assertRowCount(result, 3);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithOrder() throws FxApplicationException {
        checkJoinWithOrder("name", Arrays.asList("Martin Black", "Peter Bones", "Sandra Locke"),
                Arrays.asList(75000L, 50000L, 85000L));
        checkJoinWithOrder("name ASC", Arrays.asList("Martin Black", "Peter Bones", "Sandra Locke"),
                Arrays.asList(75000L, 50000L, 85000L));
        checkJoinWithOrder("annualSalary", Arrays.asList("Peter Bones", "Martin Black", "Sandra Locke"),
                Arrays.asList(50000L, 75000L, 85000L));
        checkJoinWithOrder("name DESC", Arrays.asList("Sandra Locke", "Peter Bones", "Martin Black"),
                Arrays.asList(85000L, 50000L, 75000L));
        checkJoinWithOrder("annualSalary DESC", Arrays.asList("Sandra Locke", "Martin Black", "Peter Bones"),
                Arrays.asList(85000L, 75000L, 50000L));
    }

    private void checkJoinWithOrder(String orderBy, List<String> expectedNames, List<Long> expectedSalaries)
            throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) ORDER BY " + orderBy);
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), expectedNames);
        assertEquals(result.collectColumnValues(2), expectedSalaries);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) " + "WHERE data.annualSalary = 50000");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).getString(), "Peter Bones");
        assertEquals(result.getColumn(0, 2).getInt(), 50000);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithDisjunction() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) "
                        + "WHERE data.annualSalary = 50000 OR person.name='Sandra Locke' " + "ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Sandra Locke"));
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 85000L));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithConjunction() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) "
                        + "WHERE data.annualSalary >= 50000 AND person.name='Peter Bones'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).getString(), "Peter Bones");
        assertEquals(result.getColumn(0, 2).getInt(), 50000);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithConjunctionTriple() throws FxApplicationException {
        // like basicJoinWithConjunction, but with three conditions, of which two reference the same table
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) "
                        + "WHERE data.annualSalary = 50000 AND person.name='Peter Bones' AND person.ssn='3721'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).getString(), "Peter Bones");
        assertEquals(result.getColumn(0, 2).getInt(), 50000);
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithNestedCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) " + "WHERE data.annualSalary = 50000"
                        + " OR (person.name = 'Martin Black' AND data.annualSalary = 75000) "
                        + "ORDER BY annualSalary");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithNestedConditionTriple() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) " + "WHERE data.annualSalary = 50000"
                        + " OR (person.name = 'Martin Black' AND data.annualSalary = 75000 AND data.ssn='5241') "
                        + "ORDER BY annualSalary");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithNestedConditions() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) "
                        + "WHERE (data.annualSalary = 50000 AND person.name = 'Peter Bones')"
                        + " OR (person.name = 'Martin Black' AND data.annualSalary = 75000) "
                        + "ORDER BY annualSalary");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void mixedCaseQuery() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "select person.name, data.annualSalary from CMIS_PERSON as person join CMIS_PERSON_DATA as data "
                        + "on (person.ssn = data.ssn) "
                        + "where (data.annualSalary = 50000 and person.name = 'Peter Bones')"
                        + " or (person.name = 'Martin Black' and data.annualSalary = 75000) "
                        + "order by annualSalary");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues("annualSalary"), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues("name"), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void delimitedIdentifiersQuery() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "select \"person\".\"name\", \"data\".\"annualSalary\" from \"CMIS_PERSON\" as \"person\" join \"CMIS_PERSON_DATA\" as \"data\" "
                        + "on (\"person\".\"ssn\" = \"data\".\"ssn\") "
                        + "where (\"data\".\"annualSalary\" = 50000 and \"person\".\"name\" = 'Peter Bones')"
                        + " or (\"person\".\"name\" = 'Martin Black' and \"data\".\"annualSalary\" = 75000) "
                        + "order by \"annualSalary\"");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues("annualSalary"), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues("name"), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicJoinWithNestedConjunctions() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT person.name, data.annualSalary FROM CMIS_PERSON AS person JOIN CMIS_PERSON_DATA AS data "
                        + "ON (person.ssn = data.ssn) "
                        + "WHERE (data.annualSalary >= 50000 AND (person.name = 'Peter Bones' OR person.name='Martin Black')) "
                        + "ORDER BY annualSalary");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 75000L));
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void joinWithReference() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT person.name, log.date, log.activity "
                + "FROM cmis_person AS person JOIN cmis_audit_log AS log " + "ON (person.id=log.person) "
                + "ORDER BY name, date");
        assertRowCount(result, 4);
        assertEquals(result.collectColumnValues(1),
                Arrays.asList("Peter Bones", "Peter Bones", "Sandra Locke", "Sandra Locke"));
        assertEquals(result.collectColumnValues(1),
                Arrays.asList("Peter Bones", "Peter Bones", "Sandra Locke", "Sandra Locke"));
        assertEquals(result.collectColumnValues(3),
                Arrays.asList("Logged in", "Logged out", "Updated article #31.5", "Updated contact #912.1"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void likeCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE name LIKE 'Peter%'");
        assertRowCount(result, 1);
        assertEquals(result.getColumn(0, 1).getString(), "Peter Bones");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void likeConditionConjunction() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT cmis:ObjectId, name FROM contactData WHERE name LIKE 'First' AND surname LIKE 'nestedCondition'");
        assertRowCount(result, 1);
        assertEquals(result.getRow(0).getColumn("name").getString(), "First");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void notLikeCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE name NOT LIKE 'Peter%' ORDER BY name");
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Alex Cervais", "Martin Black", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void limitToAssignment() throws FxApplicationException {
        final FxPK pk1 = createContactData("abc", "def");
        final FxPK pk2 = createContactData("def", "abc");
        final CmisResultSet result1 = getCmisSearchEngine().search("SELECT id FROM contactData WHERE name='def'" // should return pk1
        );
        assertRowCount(result1, 1);
        assertEquals(result1.getColumn(0, 1).getLong(), pk1.getId(),
                "Condition not constrained to values of assignment");

        final CmisResultSet result2 = getCmisSearchEngine().search("SELECT id FROM contactData WHERE name='abc'" // should return pk2
        );
        assertRowCount(result2, 1);
        assertEquals(result2.getColumn(0, 1).getLong(), pk2.getId(),
                "Condition not constrained to values of assignment");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicInCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE name IN ('Peter Bones', 'Martin Black') ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Martin Black", "Peter Bones"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicNotInCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name FROM cmis_person WHERE name NOT IN ('Peter Bones', 'Martin Black') ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Alex Cervais", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void numericInConditionWithJoin() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT p.name, d.annualSalary FROM cmis_person AS p JOIN cmis_person_data AS d ON (p.ssn=d.ssn) "
                        + "WHERE d.annualSalary IN (50000, 85000) ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Sandra Locke"));
        assertEquals(result.collectColumnValues(2), Arrays.asList(50000L, 85000L));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void numericNotInConditionWithJoin() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT p.name, d.annualSalary FROM cmis_person AS p JOIN cmis_person_data AS d ON (p.ssn=d.ssn) "
                        + "WHERE d.annualSalary NOT IN (50000, 85000) ORDER BY name");
        assertRowCount(result, 1);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Martin Black"));
        assertEquals(result.collectColumnValues(2), Arrays.asList(75000L));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void anyInStringCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name FROM cmis_person WHERE ANY email IN ('peter.bones@myprov.net', 'sandra.locke@gmx.net') ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void anyNotInStringCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name FROM cmis_person WHERE ANY email NOT IN ('peter.bones@myprov.net', 'sandra.locke@gmx.net') ORDER BY name");
        assertRowCount(result, 1);
        // peter bones has another email address, so it is returned
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void allNotInStringCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT name FROM cmis_person WHERE ANY email NOT IN ('peter.bones@myprov.net', 'peter.bones@gmail.com') ORDER BY name");
        assertRowCount(result, 1);
        // only sandra locke has an email address that is not filtered
        assertEquals(result.collectColumnValues(1), Arrays.asList("Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void multivaluedNegation() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE 'peter.bones@myprov.net' <> ANY email ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void isNullCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE email IS NULL ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Alex Cervais", "Martin Black"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void isNotNullCondition() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name FROM cmis_person WHERE email IS NOT NULL ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Peter Bones", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicSelectWithDerivedTypes() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT astring FROM cmis_type_a ORDER BY astring");
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), Arrays.asList("A", "AB", "ABC"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void basicConditionWithDerivedTypes() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT astring FROM cmis_type_a WHERE astring LIKE 'A%' ORDER BY astring");
        assertRowCount(result, 3);
        assertEquals(result.collectColumnValues(1), Arrays.asList("A", "AB", "ABC"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectFromDerivedType() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT astring, bstring FROM cmis_type_b ORDER BY astring");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("AB", "ABC"));
        assertEquals(result.collectColumnValues(2), Arrays.asList("B", "BC"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectMultivalued() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name, email FROM cmis_person ORDER BY name");
        assertRowCount(result, 4);
        assertEquals(result.collectColumnValues(1),
                Arrays.asList("Alex Cervais", "Martin Black", "Peter Bones", "Sandra Locke"));
        assertEquals(result.collectColumnValues(2),
                Arrays.asList(null, null, Arrays.asList("peter.bones@gmail.com", "peter.bones@myprov.net"),
                        Arrays.asList("sandra.locke@gmx.net")));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectMultivaluedDate() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name, date FROM cmis_multivalued ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Saturday Night Fever", "Sunday Chillout"));
        assertTrue(result.getColumn(0, 2).isAggregate(),
                "Multivalued column should be create an aggregate result value");
        assertTrue(result.getColumn(0, 2).getValues().size() == 3,
                "Expected three dates, got: " + result.getColumn(0, 2).getValues());
        assertTrue(result.getColumn(0, 2).getValue() instanceof Date,
                "Expected value type java.util.Date, got: " + result.getColumn(0, 2).getValue().getClass());
        assertTrue(result.getColumn(0, 2).getValues().iterator().next() instanceof Date,
                "Expected value type java.util.Date, got: "
                        + result.getColumn(0, 2).getValues().iterator().next().getClass());
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void propertySecurity() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT name, sensible FROM cmis_property_perm ORDER BY name");
        assertRowCount(result, 2);
        assertEquals(result.collectColumnValues(1), Arrays.asList("Protected", "Unprotected"));
        assertEquals(result.getColumn(0, 2).getValue().getClass(), FxNoAccess.class);
        assertEquals(result.getColumn(1, 2).getValue(), "Unprotected sensible");
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void versionFilter() throws FxApplicationException {
        final FxPK contentPk = new FxPK(getCmisSearchEngine()
                .search("SELECT id FROM cmis_person WHERE name='Peter Bones'").getColumn(0, 1).getLong(), FxPK.MAX);
        final FxPK newPK = getContentEngine().createNewVersion(getContentEngine().load(contentPk));
        try {
            final CmisResultSet result = getCmisSearchEngine()
                    .search("SELECT id, version, name FROM cmis_person WHERE name='Peter Bones'");
            assertRowCount(result, 1);
            assertEquals(result.getColumn(0, 1).getLong(), newPK.getId());
            assertEquals(result.getColumn(0, 2).getInt(), newPK.getVersion(),
                    "Max version should have been selected");
            assertEquals(result.getColumn(0, 3).getString(), "Peter Bones");
        } finally {
            try {
                FxContext.get().runAsSystem();
                getContentEngine().removeVersion(newPK);
            } finally {
                FxContext.get().stopRunAsSystem();
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectAll() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search("SELECT * FROM cmis_person ORDER BY name");
        assertRowCount(result, 4);
        assertEquals(result.getColumnAliases().subList(0, 3), Arrays.asList("name", "ssn", "email"));
        assertEquals(result.collectColumnValues("name"),
                Arrays.asList("Alex Cervais", "Martin Black", "Peter Bones", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectAllWithAlias() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT p.id, p.* FROM cmis_person AS p ORDER BY name");
        assertRowCount(result, 4);
        assertEquals(result.getColumnAliases().subList(0, 4), Arrays.asList("id", "name", "ssn", "email"));
        assertEquals(result.collectColumnValues("name"),
                Arrays.asList("Alex Cervais", "Martin Black", "Peter Bones", "Sandra Locke"));
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectAllWithJoin() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine().search(
                "SELECT p.*, data.* FROM cmis_person AS p JOIN cmis_person_data AS data ON (p.ssn=data.ssn) ORDER BY name");
        assertRowCount(result, 3);
        assertEquals(
                Lists.newArrayList(Iterables.concat(result.getColumnAliases().subList(0, 3),
                        result.getColumnAliases().subList(result.getRow(0).indexOf("entrydate") - 2,
                                result.getRow(0).indexOf("entryDate") + 1))),
                // TODO: table alias should be set in column alias
                Arrays.asList("name", "ssn", "email", "ssn", "entrydate", "annualsalary"));
        assertEquals(result.collectColumnValues("name"),
                Arrays.asList("Martin Black", "Peter Bones", "Sandra Locke"));
        assertEquals(result.collectColumnValues("annualSalary"), Arrays.asList(75000L, 50000L, 85000L));
    }

    @SuppressWarnings({ "unchecked" })
    @Test(groups = { "search", "cmis", "ejb" })
    public void selectSystemProperties() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT id, version, acl, step, typedef, created_at, created_by, "
                        + "modified_at, modified_by FROM cmis_person");
        assertRowCount(result, 4);
        for (CmisResultRow row : result) {
            final FxContent content = EJBLookup.getContentEngine()
                    .load(new FxPK(row.getColumn(1).getLong(), row.getColumn(2).getInt()));
            assertEquals(row.getColumn("step").getLong(), content.getStepId());
            assertEquals(row.getColumn("typedef").getLong(), content.getTypeId());
            assertEquals(row.getColumn("created_at").getDate().getTime(),
                    content.getLifeCycleInfo().getCreationTime());
            assertEquals(row.getColumn("created_by").getLong(), content.getLifeCycleInfo().getCreatorId());
            assertEquals(row.getColumn("modified_at").getDate().getTime(),
                    content.getLifeCycleInfo().getModificationTime());
            assertEquals(row.getColumn("modified_by").getLong(), content.getLifeCycleInfo().getModificatorId());
            assertEquals(new HashSet(row.getColumn("acl").getValues()), new HashSet(content.getAclIds()),
                    "Expected ACLs: " + row.getColumn("acl").getValues() + ", got: " + content.getAclIds());
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void inSelectMany() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT id, selectManySearchProp FROM SearchTest");
        assertTrue(result.getRowCount() > 0);
        final List<FxSelectListItem> queryItems = new ArrayList<FxSelectListItem>();
        for (CmisResultRow row : result) {
            final List<FxSelectListItem> selected = ((SelectMany) row.getColumn(2).getValue()).getSelected();
            if (selected.size() > 2) {
                queryItems.add(selected.get(0));
                queryItems.add(selected.get(1));
                if (getExpectedMatchRows(result, queryItems) == result.getRowCount()
                        || getExpectedPartialMatches(result, queryItems) == result.getRowCount()) {
                    // exact or partial match for this selection would match all rows - not suitable for a query test
                    queryItems.clear();
                } else {
                    break;
                }
            }
        }
        // count rows that contain both selected IDs
        final int matchingRows = getExpectedMatchRows(result, queryItems);
        final int partialMatchingRows = getExpectedPartialMatches(result, queryItems);
        assertFalse(queryItems.isEmpty(), "No suitable SelectMany instance found");
        assertTrue(matchingRows > 0);

        final CmisResultSet condResult = getCmisSearchEngine()
                .search("SELECT id, selectManySearchProp FROM SearchTest WHERE selectManySearchProp IN ("
                        + StringUtils.join(FxSharedUtils.getSelectableObjectIdList(queryItems), ',') + ")");
        assertEquals(condResult.getRowCount(), matchingRows,
                "Wrong result row count for query on " + queryItems + ", result:\n" + formatResult(condResult));

        // select other rows using NOT IN
        final CmisResultSet invResult = getCmisSearchEngine()
                .search("SELECT id, selectManySearchProp FROM SearchTest WHERE selectManySearchProp NOT IN ("
                        + StringUtils.join(FxSharedUtils.getSelectableObjectIdList(queryItems), ',') + ")");
        assertEquals(invResult.getRowCount(), result.getRowCount() - partialMatchingRows,
                "Wrong result row count for NOT IN query on " + queryItems + ", result:\n"
                        + formatResult(invResult));
        assertTrue(invResult.getRowCount() > 0, "NOT IN query returned no rows");

    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void useNullProperty_FX814() throws FxApplicationException {
        final FxTypeEdit type;
        try {
            FxContext.startRunningAsSystem();
            type = FxTypeEdit.createNew("FX814").save();
        } finally {
            FxContext.stopRunningAsSystem();
        }
        try {
            // use unmapped CMIS property in order by
            getCmisSearchEngine().search("SELECT cmis:ObjectId, cmis:Name " + "FROM FX814 " + "ORDER BY cmis:Name");

            // use unmapped CMIS property in where
            getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId, cmis:Name " + "FROM FX814 " + "WHERE cmis:Name IS NOT NULL");

        } finally {
            try {
                FxContext.startRunningAsSystem();
                EJBLookup.getTypeEngine().remove(type.getId());
            } finally {
                FxContext.stopRunningAsSystem();
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void inFolderTest() throws FxApplicationException {
        final List<Long> nodeIds = Lists.newArrayList(createTestFolderContents("searchFolderTest"));
        try {
            final CmisResultSet result = getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId FROM cmis:document WHERE IN_FOLDER('/')");
            assertTrue(result.getRowCount() > 0, "No rows returned for root tree query");

            nodeIds.add(getTreeEngine().createNodes(FxTreeMode.Edit, FxTreeNode.ROOT_NODE, -1,
                    "/searchFolderTestEmpty")[0]);
            final CmisResultSet emptyResult = getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId FROM cmis:document WHERE IN_FOLDER('/searchFolderTestEmpty')");
            assertTrue(emptyResult.getRowCount() == 0, "No rows should be returned for empty folder");

            // new PK should be returned
            assertRowCount(getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId FROM cmis:document WHERE IN_FOLDER('/searchFolderTest')"), 1);
        } finally {
            for (long id : nodeIds) {
                EJBLookup.getTreeEngine().remove(FxTreeMode.Edit, id, FxTreeRemoveOp.Remove, true);
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void inTreeTest() throws FxApplicationException {
        final List<Long> nodeIds = createTestFolderContents("inTreeTest");
        try {
            final CmisResultSet result = getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId FROM cmis:document WHERE IN_TREE('/')");
            assertTrue(result.getRowCount() > 0, "No rows returned for root tree query");

            final CmisResultSet directChildren = getCmisSearchEngine()
                    .search("SELECT cmis:ObjectId FROM cmis:document WHERE IN_FOLDER('/')");
            assertTrue(directChildren.getRowCount() < result.getRowCount(),
                    "More direct children than total children: " + directChildren.getRowCount() + " >= "
                            + result.getRowCount());
        } finally {
            for (long id : nodeIds) {
                getTreeEngine().remove(FxTreeMode.Edit, id, FxTreeRemoveOp.Remove, true);
            }
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectPropertyTest() throws FxApplicationException {
        final CmisResultSet result = getCmisSearchEngine()
                .search("SELECT cmis:objectId, selectOneSearchProp FROM SearchTest");
        assertTrue(result.getRowCount() > 0, "No rows returned");
        for (CmisResultRow row : result) {
            final Object value = row.getColumn(2).getValue();
            assertTrue(value != null, "Value was null");
            assertTrue(value instanceof FxSelectListItem, "Unexpected return type: " + value.getClass());
        }
    }

    @Test(groups = { "search", "cmis", "ejb" })
    public void selectMultiBinaryTest() throws FxApplicationException {
        // attempts to select a binary with max multiplicity > 1
        try {
            FxContext.startRunningAsSystem();
            final FxTypeEdit type = FxTypeEdit.createNew("selectMultiBinaryTest").save();
            final ContentEngine ce = EJBLookup.getContentEngine();
            try {
                type.addProperty("binaries", FxDataType.Binary).setMultiplicity(FxMultiplicity.MULT_0_N).save();

                final FxContent co = ce.initialize(type.getId());
                setBinaryProperty(co, "/binaries[1]", "abc".getBytes());
                setBinaryProperty(co, "/binaries[2]", "def".getBytes());
                ce.save(co);

                final CmisResultSet result = getCmisSearchEngine().search("SELECT * FROM " + type.getName());
                assertTrue(result.getRowCount() > 0, "No rows returned");
            } finally {
                ce.removeForType(type.getId());
                EJBLookup.getTypeEngine().remove(type.getId());
            }
        } finally {
            FxContext.stopRunningAsSystem();
        }
    }

    private void setBinaryProperty(final FxContent co, String parameter, byte[] bytes) throws FxStreamException {
        co.setValue(parameter, new FxBinary(false, new BinaryDescriptor("test1", new ByteArrayInputStream(bytes))));
    }

    private List<Long> createTestFolderContents(String folderName) throws FxApplicationException {
        // create a content in the root folder
        final FxPK rootPk = getContentEngine().save(getContentEngine().initialize("cmis_person"));
        final long rootNodeId = getTreeEngine()
                .save(FxTreeNodeEdit.createNew("").setParentNodeId(FxTreeNode.ROOT_NODE).setReference(rootPk));

        // create a folder with another content instance
        final long folderId = getTreeEngine().createNodes(FxTreeMode.Edit, FxTreeNode.ROOT_NODE, -1,
                "/" + folderName)[0];
        final FxPK pk = getContentEngine().save(getContentEngine().initialize("cmis_person"));
        getTreeEngine().save(FxTreeNodeEdit.createNew("").setParentNodeId(folderId).setReference(pk));

        return Arrays.asList(rootNodeId, folderId);
    }

    private int getExpectedPartialMatches(CmisResultSet result, final List<FxSelectListItem> queryItems) {
        return Iterables.size(Iterables.filter(result, new Predicate<CmisResultRow>() {
            @Override
            public boolean apply(CmisResultRow row) {
                final SelectMany selectMany = (SelectMany) row.getColumn(2).getValue();
                return !Collections.disjoint(queryItems, selectMany.getSelected());
            }
        }));
    }

    private int getExpectedMatchRows(CmisResultSet result, final List<FxSelectListItem> queryItems) {
        return Iterables.size(Iterables.filter(result, new Predicate<CmisResultRow>() {
            @Override
            public boolean apply(CmisResultRow row) {
                final SelectMany selectMany = (SelectMany) row.getColumn(2).getValue();
                return selectMany.getSelected().containsAll(queryItems);
            }
        }));
    }

    private void assertRowCount(CmisResultSet result, int expectedRows) {
        assertEquals(result.getRowCount(), expectedRows,
                "Expected " + expectedRows + " result row, got:\n" + formatResult(result));
    }

    private FxPK createContactData(String surname, String name) throws FxApplicationException {
        try {
            FxContext.get().runAsSystem(); //have to be supervisor to create contact data for "others"
            final FxPK pk = getContentEngine().initialize(FxType.CONTACTDATA).setValue("/surname", surname)
                    .setValue("/name", name).save().getPk();
            testInstances.add(pk);
            return pk;
        } finally {
            FxContext.get().stopRunAsSystem();
        }
    }

    private FxPK createArticle(String title, String text) throws FxApplicationException {
        final FxPK pk = getContentEngine().initialize("ARTICLE").setValue("/title", title)
                .setValue("/longtext", text).save().getPk();
        testInstances.add(pk);
        return pk;
    }

    private String formatResult(CmisResultSet result) {
        return StringUtils.join(result.getRows(), "\n") + "\n";
    }

}