blob: 0a3438422210f0da9500d8ec028b0d7e24147e33 [file] [log] [blame]
/*
* 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
*
* https://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.apache.directory.api.ldap.subtree;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.ObjectClass;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.api.ldap.model.subtree.AndRefinement;
import org.apache.directory.api.ldap.model.subtree.ItemRefinement;
import org.apache.directory.api.ldap.model.subtree.NotRefinement;
import org.apache.directory.api.ldap.model.subtree.OrRefinement;
import org.apache.directory.api.ldap.model.subtree.Refinement;
import org.apache.directory.api.ldap.model.subtree.SubtreeSpecification;
import org.apache.directory.api.ldap.model.subtree.SubtreeSpecificationParser;
import org.apache.directory.api.ldap.schema.loader.JarLdifSchemaLoader;
import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
/**
* Unit tests class for Subtree Specification parser (wrapper).
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
@Execution( ExecutionMode.CONCURRENT )
public class SubtreeSpecificationParserTest
{
/** the ss parser wrapper */
private static SubtreeSpecificationParser parser;
/** A valid empty specification with single white space between brackets */
private static final String EMPTY_SPEC = "{ }";
/** A valid specification only with base set */
private static final String SPEC_WITH_BASE = "{ base \"ou=system\" }";
/** An invalid specification with missing white space and base set */
private static final String INVALID_SPEC_WITH_BASE_AND_MISSING_WS = "{ base\"ou=system\"}";
/** A valid specification with some specific exclusions set */
private static final String SPEC_WITH_SPECIFICEXCLUSIONS = "{ specificExclusions { chopAfter:\"cn=gh\", chopBefore:\"cn=cd\" } }";
/** A valid specification with empty specific exclusions set */
private static final String SPEC_WITH_EMPTY_SPECIFICEXCLUSIONS = "{ specificExclusions { } }";
/** A valid specification with minimum and maximum set */
private static final String SPEC_WITH_MINIMUM_AND_MAXIMUM = "{ minimum 1, maximum 2 }";
/** A valid specification with base and minimum and maximum set */
private static final String SPEC_WITH_BASE_AND_MINIMUM_AND_MAXIMUM = "{ base \"ou=ORGANIZATION UNIT\", minimum 1, maximum 2 }";
/**
* A valid specification with base and specific exclusions and minimum and
* maximum set
*/
private static final String SPEC_WITH_BASE_AND_SPECIFICEXCLUSIONS_AND_MINIMUM_AND_MAXIMUM = "{ base \"ou=people\", specificExclusions { chopBefore:\"cn=y\""
+ ", chopAfter:\"sn=l\", chopBefore:\"c=z\", chopAfter:\"l=m\" }, minimum 7, maximum 77 }";
/** A valid specification with refinement set */
private static final String SPEC_WITH_REFINEMENT = "{ base \"ou=system\", specificationFilter and:{ and:{ item:2.5.6.0"
+ ", or:{ item:2.5.6.1, item:person } }, not: item:2.5.6.2 } }";
/** A valid specification with base and an empty refinement set */
private static final String SPEC_WITH_BASE_AND_EMPTY_REFINEMENT = "{ base \"ou=system\", specificationFilter and:{ } }";
/** A valid specification with ALL IN ONE */
private static final String SPEC_WITH_ALL_IN_ONE = "{ base \"ou=departments\""
+ ", specificExclusions { chopBefore:\"cn=y\", chopAfter:\"sn=l\", chopBefore:\"c=z\", chopAfter:\"l=m\" }"
+ ", minimum 7, maximum 77"
+ ", specificationFilter and:{ and:{ item:2.5.6.0, or:{ item:2.5.6.1, item:2.5.6.2 } }, not: item:2.5.6.3 } }";
/** An valid specification with unordinary component order */
private static final String SPEC_ORDER_OF_COMPONENTS_DOES_NOT_MATTER = "{ base \"ou=system\", minimum 3, specificExclusions { chopBefore:\"cn=y\" } }";
/** An invalid specification with completely unrelated content */
private static final String INVALID_SILLY_THING = "How much wood would a wood chuck chuck if a wood chuck would chuck wood?";
/** holds multithreaded success value */
boolean isSuccessMultithreaded = true;
/** The schema manager */
private static SchemaManager schemaManager;
/** Some global OC */
private static ObjectClass topOC; // 2.5.6.0
private static ObjectClass aliasOC; // 2.5.6.1
private static ObjectClass countryOC; // 2.5.6.2
private static ObjectClass personOC; // 2.5.6.6
/**
* Initialization
*/
@BeforeAll
public static void init() throws Exception
{
JarLdifSchemaLoader loader = new JarLdifSchemaLoader();
schemaManager = new DefaultSchemaManager( loader );
schemaManager.loadAllEnabled();
parser = new SubtreeSpecificationParser( schemaManager );
topOC = schemaManager.lookupObjectClassRegistry( "top" );
aliasOC = schemaManager.lookupObjectClassRegistry( "alias" );
countryOC = schemaManager.lookupObjectClassRegistry( "country" );
personOC = schemaManager.lookupObjectClassRegistry( "person" );
}
/**
* Tests the parser with a valid empty specification.
*/
@Test
public void testEmptySpec() throws Exception
{
SubtreeSpecification ss = parser.parse( EMPTY_SPEC );
assertNotNull( ss );
// try a second time
ss = parser.parse( EMPTY_SPEC );
assertNotNull( ss );
// try a third time
ss = parser.parse( EMPTY_SPEC );
assertNotNull( ss );
}
/**
* Tests the parser with a valid specification with base set.
*/
@Test
public void testSpecWithBase() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE );
assertNotNull( ss );
assertEquals( "ou=system", ss.getBase().toString() );
}
/**
* Tests the parser with an invalid specification with missing white spaces
* and base set.
*/
@Test
public void testInvalidSpecWithBaseAndMissingWS() throws Exception
{
try
{
parser.parse( INVALID_SPEC_WITH_BASE_AND_MISSING_WS );
fail( "testInvalidSpecWithBaseAndMissingWS() should never come here..." );
}
catch ( ParseException e )
{
assertNotNull( e );
}
}
/**
* Tests the parser with a valid specification with some specific exclusions
* set.
*/
@Test
public void testSpecWithSpecificExclusions() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
assertFalse( ss.getChopBeforeExclusions().isEmpty() );
assertFalse( ss.getChopAfterExclusions().isEmpty() );
assertTrue( ss.getChopBeforeExclusions().contains( new Dn( schemaManager, "cn=cd" ) ) );
assertTrue( ss.getChopAfterExclusions().contains( new Dn( schemaManager, "cn=gh" ) ) );
// try a second time
ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
assertFalse( ss.getChopBeforeExclusions().isEmpty() );
assertFalse( ss.getChopAfterExclusions().isEmpty() );
assertTrue( ss.getChopBeforeExclusions().contains( new Dn( schemaManager, "cn=cd" ) ) );
assertTrue( ss.getChopAfterExclusions().contains( new Dn( schemaManager, "cn=gh" ) ) );
// try a third time
ss = parser.parse( SPEC_WITH_SPECIFICEXCLUSIONS );
assertFalse( ss.getChopBeforeExclusions().isEmpty() );
assertFalse( ss.getChopAfterExclusions().isEmpty() );
assertTrue( ss.getChopBeforeExclusions().contains( new Dn( schemaManager, "cn=cd" ) ) );
assertTrue( ss.getChopAfterExclusions().contains( new Dn( schemaManager, "cn=gh" ) ) );
}
/**
* Tests the parser with a valid specification with an empty specific
* exclusions set.
*/
@Test
public void testSpecWithEmptySpecificExclusions() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_EMPTY_SPECIFICEXCLUSIONS );
assertNotNull( ss );
assertTrue( ss.getChopBeforeExclusions().isEmpty() );
}
/**
* Tests the parser with a valid specification with minimum and maximum set.
*/
@Test
public void testSpecWithMinimumAndMaximum() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
assertEquals( 1, ss.getMinBaseDistance() );
assertEquals( 2, ss.getMaxBaseDistance() );
// try a second time
ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
assertEquals( 1, ss.getMinBaseDistance() );
assertEquals( 2, ss.getMaxBaseDistance() );
// try a third time
ss = parser.parse( SPEC_WITH_MINIMUM_AND_MAXIMUM );
assertEquals( 1, ss.getMinBaseDistance() );
assertEquals( 2, ss.getMaxBaseDistance() );
}
/**
* Tests the parser with a valid specification with base and minimum and
* maximum set.
*/
@Test
public void testWithBaseAndMinimumAndMaximum() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_MINIMUM_AND_MAXIMUM );
assertEquals( new Dn( "ou=ORGANIZATION UNIT" ).getName(), ss.getBase().getName() );
assertEquals( 1, ss.getMinBaseDistance() );
assertEquals( 2, ss.getMaxBaseDistance() );
}
/**
* Tests the parser with a valid specification with base and specific
* exclusions and minimum and maximum set.
*/
@Test
public void testSpecWithBaseAndSpecificExclusionsAndMinimumAndMaximum() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_SPECIFICEXCLUSIONS_AND_MINIMUM_AND_MAXIMUM );
assertNotNull( ss );
assertEquals( "ou=people", ss.getBase().toString() );
assertTrue( ss.getChopBeforeExclusions().contains( new Dn( schemaManager, "cn=y" ) ) );
assertTrue( ss.getChopBeforeExclusions().contains( new Dn( schemaManager, "c=z" ) ) );
assertTrue( ss.getChopAfterExclusions().contains( new Dn( schemaManager, "sn=l" ) ) );
assertTrue( ss.getChopAfterExclusions().contains( new Dn( schemaManager, "l=m" ) ) );
assertEquals( 7, ss.getMinBaseDistance() );
assertEquals( 77, ss.getMaxBaseDistance() );
}
/**
* Tests the parser with a valid specification with refinement set.
*/
@Test
public void testSpecWithRefinement() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_REFINEMENT );
// The items
Refinement topItem = new ItemRefinement( topOC );
Refinement aliasItem = new ItemRefinement( aliasOC );
Refinement personItem = new ItemRefinement( personOC );
Refinement countryItem = new ItemRefinement( countryOC );
// The inner OR refinement or:{item:2.5.6.1, item:person}
List<Refinement> orList = new ArrayList<Refinement>();
orList.add( aliasItem );
orList.add( personItem );
Refinement orRefinement = new OrRefinement( orList );
// The inner AND refinement and:{ item:2.5.6.0, or:... }
List<Refinement> innerAndList = new ArrayList<Refinement>();
innerAndList.add( topItem );
innerAndList.add( orRefinement );
Refinement innerAndRefinement = new AndRefinement( innerAndList );
// The NOT refinement not:item:2.5.6.2
Refinement notRefinement = new NotRefinement( countryItem );
// The outer AND refinement and:{and:..., not:...}
List<Refinement> outerAndList = new ArrayList<Refinement>();
outerAndList.add( innerAndRefinement );
outerAndList.add( notRefinement );
StringBuilder buffer = new StringBuilder();
ss.getRefinement().printRefinementToBuffer( buffer );
//assertEquals( outerAndRefinement.toString(), buffer );
assertEquals( "and: { and: { item: 2.5.6.0, or: { item: 2.5.6.1, item: person } }, not: item: 2.5.6.2 }",
buffer.toString() );
}
/**
* Tests the parser with a valid specification with base and empty
* refinement set.
*/
@Test
public void testSpecWithBaseAndEmptyRefinement() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_BASE_AND_EMPTY_REFINEMENT );
assertEquals( "ou=system", ss.getBase().toString() );
}
/**
* Tests the parser with a valid specification with all components set.
*/
@Test
public void testSpecWithAllInOne() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_WITH_ALL_IN_ONE );
assertNotNull( ss );
}
/**
* Tests the parser with a valid specification with unordinary component
* order.
*/
@Test
public void testSpecOrderOfComponentsDoesNotMatter() throws Exception
{
SubtreeSpecification ss = parser.parse( SPEC_ORDER_OF_COMPONENTS_DOES_NOT_MATTER );
assertNotNull( ss );
}
/**
* Tests the parser with an invalid specification with silly things in.
*/
@Test
public void testInvalidSillyThing() throws Exception
{
try
{
parser.parse( INVALID_SILLY_THING );
fail( "testInvalidSillyThing() should never come here..." );
}
catch ( ParseException e )
{
assertNotNull( e );
}
}
/**
* Test reusability, especially if the state is resetted.
*/
@Test
public void testReusabiltiy() throws Exception
{
Dn firstDn = new Dn( schemaManager, "cn=l" );
String firstExclusion = "{ specificExclusions { chopAfter:\"cn=l\" } }";
SubtreeSpecification firstSpec = parser.parse( firstExclusion );
assertEquals( 1, firstSpec.getChopAfterExclusions().size() );
assertEquals( firstDn, firstSpec.getChopAfterExclusions().iterator().next() );
Dn secondDn = new Dn( schemaManager, "l=y" );
String secondExclusion = "{ specificExclusions { chopAfter:\"l=y\" } }";
SubtreeSpecification secondSpec = parser.parse( secondExclusion );
assertEquals( 1, secondSpec.getChopAfterExclusions().size() );
assertEquals( secondDn, secondSpec.getChopAfterExclusions().iterator().next() );
}
/**
* Tests the multithreaded use of a single parser.
*/
@Test
public void testMultiThreaded() throws Exception
{
// start up and track all threads (40 threads)
List<Thread> threads = new ArrayList<Thread>();
for ( int ii = 0; ii < 10; ii++ )
{
Thread t0 = new Thread( new ParseSpecification( EMPTY_SPEC ) );
Thread t1 = new Thread( new ParseSpecification( SPEC_WITH_SPECIFICEXCLUSIONS ) );
Thread t2 = new Thread( new ParseSpecification( SPEC_WITH_MINIMUM_AND_MAXIMUM ) );
Thread t3 = new Thread( new ParseSpecification( SPEC_WITH_ALL_IN_ONE ) );
threads.add( t0 );
threads.add( t1 );
threads.add( t2 );
threads.add( t3 );
t0.start();
t1.start();
t2.start();
t3.start();
}
// wait until all threads have died
boolean hasLiveThreads = false;
do
{
hasLiveThreads = false;
for ( int ii = 0; ii < threads.size(); ii++ )
{
Thread t = threads.get( ii );
hasLiveThreads = hasLiveThreads || t.isAlive();
}
}
while ( hasLiveThreads );
// check that no one thread failed to parse and generate a SS object
assertTrue( isSuccessMultithreaded );
}
/**
* Used to test multithreaded use of a single parser.
*/
class ParseSpecification implements Runnable
{
private final String specStr;
SubtreeSpecification result;
public ParseSpecification( String specStr )
{
this.specStr = specStr;
}
public void run()
{
try
{
result = parser.parse( specStr );
}
catch ( ParseException e )
{
e.printStackTrace();
}
isSuccessMultithreaded = isSuccessMultithreaded && ( result != null );
}
}
}