/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2008 Sun Microsystems, Inc.
|
* Portions Copyright 2014-2015 ForgeRock AS
|
*/
|
package org.opends.server.admin.server;
|
|
import java.util.List;
|
|
import javax.naming.OperationNotSupportedException;
|
import javax.naming.ldap.LdapName;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.opendj.config.server.ConfigException;
|
import org.forgerock.opendj.ldap.ResultCode;
|
import org.opends.server.TestCaseUtils;
|
import org.opends.server.admin.AdminTestCase;
|
import org.opends.server.admin.TestCfg;
|
import org.opends.server.admin.TestChildCfg;
|
import org.opends.server.admin.TestChildCfgDefn;
|
import org.opends.server.admin.TestParentCfg;
|
import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
|
import org.opends.server.admin.std.server.RootCfg;
|
import org.opends.server.core.AddOperation;
|
import org.forgerock.opendj.config.server.ConfigChangeResult;
|
import org.opends.server.types.Entry;
|
import org.testng.Assert;
|
import org.testng.annotations.AfterClass;
|
import org.testng.annotations.BeforeClass;
|
import org.testng.annotations.Test;
|
|
import static org.opends.server.protocols.internal.InternalClientConnection.*;
|
import static org.testng.Assert.*;
|
|
/**
|
* Test cases for constraints on the server-side.
|
*/
|
public final class ConstraintTest extends AdminTestCase {
|
|
/** Child DN. */
|
private static final String TEST_CHILD_1_DN = "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config";
|
|
|
|
/**
|
* A test child add listener.
|
*/
|
private static class AddListener implements
|
ConfigurationAddListener<TestChildCfg> {
|
|
/** {@inheritDoc} */
|
@Override
|
public ConfigChangeResult applyConfigurationAdd(TestChildCfg configuration) {
|
return new ConfigChangeResult();
|
}
|
|
|
|
/** {@inheritDoc} */
|
@Override
|
public boolean isConfigurationAddAcceptable(TestChildCfg configuration,
|
List<LocalizableMessage> unacceptableReasons) {
|
return true;
|
}
|
|
}
|
|
|
|
/**
|
* A test child delete listener.
|
*/
|
private static class DeleteListener implements
|
ConfigurationDeleteListener<TestChildCfg> {
|
|
/** {@inheritDoc} */
|
@Override
|
public ConfigChangeResult applyConfigurationDelete(
|
TestChildCfg configuration) {
|
return new ConfigChangeResult();
|
}
|
|
|
|
/** {@inheritDoc} */
|
@Override
|
public boolean isConfigurationDeleteAcceptable(TestChildCfg configuration,
|
List<LocalizableMessage> unacceptableReasons) {
|
return true;
|
}
|
|
}
|
|
|
|
/**
|
* A test child change listener.
|
*/
|
private static class ChangeListener implements
|
ConfigurationChangeListener<TestChildCfg> {
|
|
/** {@inheritDoc} */
|
@Override
|
public ConfigChangeResult applyConfigurationChange(
|
TestChildCfg configuration) {
|
return new ConfigChangeResult();
|
}
|
|
|
|
/** {@inheritDoc} */
|
@Override
|
public boolean isConfigurationChangeAcceptable(TestChildCfg configuration,
|
List<LocalizableMessage> unacceptableReasons) {
|
return true;
|
}
|
|
}
|
|
/** Test child 1 LDIF. */
|
private static final String[] TEST_CHILD_1 = new String[] {
|
"dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
|
"objectclass: top",
|
"objectclass: ds-cfg-test-child-dummy",
|
"cn: test child 1",
|
"ds-cfg-enabled: true",
|
"ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
|
"ds-cfg-attribute-type: description",
|
"ds-cfg-conflict-behavior: virtual-overrides-real"
|
};
|
|
/** Test LDIF. */
|
private static final String[] TEST_LDIF = new String[] {
|
// Base entries.
|
"dn: cn=test parents,cn=config",
|
"objectclass: top",
|
"objectclass: ds-cfg-branch",
|
"cn: test parents",
|
"",
|
// Parent 1 - uses default values for
|
// optional-multi-valued-dn-property.
|
"dn: cn=test parent 1,cn=test parents,cn=config",
|
"objectclass: top",
|
"objectclass: ds-cfg-test-parent-dummy",
|
"cn: test parent 1",
|
"ds-cfg-enabled: true",
|
"ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
|
"ds-cfg-attribute-type: description",
|
"ds-cfg-conflict-behavior: virtual-overrides-real",
|
"",
|
// Child base entries.
|
"dn:cn=test children,cn=test parent 1,cn=test parents,cn=config",
|
"objectclass: top",
|
"objectclass: ds-cfg-branch",
|
"cn: test children",
|
"",
|
};
|
|
/** JNDI LDAP context. */
|
private JNDIDirContextAdaptor adaptor;
|
|
|
|
/**
|
* Sets up tests
|
*
|
* @throws Exception
|
* If the server could not be initialized.
|
*/
|
@BeforeClass
|
public void setUp() throws Exception {
|
// This test suite depends on having the schema available, so
|
// we'll start the server.
|
TestCaseUtils.startServer();
|
TestCfg.setUp();
|
|
// Add test managed objects.
|
TestCaseUtils.addEntries(TEST_LDIF);
|
}
|
|
|
|
/**
|
* Tears down test environment.
|
*
|
* @throws Exception
|
* If the test entries could not be removed.
|
*/
|
@AfterClass
|
public void tearDown() throws Exception {
|
TestCfg.cleanup();
|
|
// Remove test entries.
|
deleteSubtree("cn=test parents,cn=config");
|
}
|
|
|
|
/**
|
* Tests that retrieval can succeed.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testGetManagedObjectSuccess() throws Exception {
|
MockConstraint constraint = new MockConstraint(true, false);
|
|
try {
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
TestCfg.addConstraint(constraint);
|
|
TestParentCfg parent = getParent("test parent 1");
|
parent.getTestChild("test child 1");
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
|
|
/**
|
* Tests that retrieval can fail.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testGetManagedObjectFail() throws Exception {
|
MockConstraint constraint = new MockConstraint(false, true);
|
|
try {
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
TestCfg.addConstraint(constraint);
|
|
TestParentCfg parent = getParent("test parent 1");
|
parent.getTestChild("test child 1");
|
} catch (ConfigException e) {
|
Throwable cause = e.getCause();
|
if (cause instanceof ConstraintViolationException) {
|
ConstraintViolationException cve = (ConstraintViolationException) cause;
|
Assert.assertEquals(cve.getMessages().size(), 1);
|
Assert.assertSame(cve.getManagedObject().getManagedObjectDefinition(),
|
TestChildCfgDefn.getInstance());
|
} else {
|
// Wrong cause.
|
throw e;
|
}
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
|
|
/**
|
* Tests that an add constraint can succeed.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testAddConstraintSuccess() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
AddListener listener = new AddListener();
|
parent.addTestChildAddListener(listener);
|
|
MockConstraint constraint = new MockConstraint(true, false);
|
TestCfg.addConstraint(constraint);
|
|
try {
|
try {
|
// Add the entry.
|
addEntry(ResultCode.SUCCESS, TEST_CHILD_1);
|
} finally {
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
parent.removeTestChildAddListener(listener);
|
}
|
}
|
|
|
|
/**
|
* Tests that an add constraint can fail.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testAddConstraintFail() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
AddListener listener = new AddListener();
|
parent.addTestChildAddListener(listener);
|
|
MockConstraint constraint = new MockConstraint(false, true);
|
TestCfg.addConstraint(constraint);
|
|
try {
|
try {
|
// Add the entry.
|
addEntry(ResultCode.UNWILLING_TO_PERFORM, TEST_CHILD_1);
|
} finally {
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
parent.removeTestChildAddListener(listener);
|
}
|
}
|
|
|
|
/**
|
* Tests that a delete constraint can succeed.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testDeleteConstraintSuccess() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
DeleteListener listener = new DeleteListener();
|
parent.addTestChildDeleteListener(listener);
|
|
MockConstraint constraint = new MockConstraint(false, true);
|
TestCfg.addConstraint(constraint);
|
|
try {
|
// Add the entry.
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
|
// Now delete it - this should trigger the constraint.
|
deleteSubtree(TEST_CHILD_1_DN);
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
parent.removeTestChildDeleteListener(listener);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
|
|
/**
|
* Tests that a delete constraint can fail.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testDeleteConstraintFail() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
DeleteListener listener = new DeleteListener();
|
parent.addTestChildDeleteListener(listener);
|
|
MockConstraint constraint = new MockConstraint(true, false);
|
TestCfg.addConstraint(constraint);
|
|
try {
|
// Add the entry.
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
try {
|
// Now delete it - this should trigger the constraint.
|
deleteSubtree(TEST_CHILD_1_DN);
|
|
// Should not have succeeded.
|
Assert.fail("Delete constraint failed to prevent deletion");
|
} catch (OperationNotSupportedException e) {
|
// Ignore - this is the expected exception.
|
}
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
parent.removeTestChildDeleteListener(listener);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
|
|
/**
|
* Tests that a modify constraint can succeed.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testChangeConstraintSuccess() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
|
MockConstraint constraint = new MockConstraint(true, false);
|
|
try {
|
// Add the entry.
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
TestChildCfg child = parent.getTestChild("test child 1");
|
|
TestCfg.addConstraint(constraint);
|
ChangeListener listener = new ChangeListener();
|
child.addChangeListener(listener);
|
|
// Now modify it.
|
String[] changes = new String[] {
|
"dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
|
"changetype: modify",
|
"replace: ds-cfg-base-dn",
|
"ds-cfg-base-dn: dc=new value 1,dc=com",
|
"ds-cfg-base-dn: dc=new value 2,dc=com",
|
"-",
|
"replace: ds-cfg-group-dn",
|
"ds-cfg-group-dn: dc=new value 3,dc=com",
|
"ds-cfg-group-dn: dc=new value 4,dc=com"
|
};
|
|
int result = TestCaseUtils.applyModifications(true, changes);
|
Assert.assertEquals(result, ResultCode.SUCCESS.intValue());
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
|
|
/**
|
* Tests that a modify constraint can fail.
|
*
|
* @throws Exception
|
* If the test unexpectedly fails.
|
*/
|
@Test
|
public void testChangeConstraintFail() throws Exception {
|
TestParentCfg parent = getParent("test parent 1");
|
MockConstraint constraint = new MockConstraint(false, true);
|
|
try {
|
// Add the entry.
|
TestCaseUtils.addEntry(TEST_CHILD_1);
|
TestChildCfg child = parent.getTestChild("test child 1");
|
|
TestCfg.addConstraint(constraint);
|
ChangeListener listener = new ChangeListener();
|
child.addChangeListener(listener);
|
|
// Now modify it.
|
String[] changes = new String[] {
|
"dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
|
"changetype: modify",
|
"replace: ds-cfg-base-dn",
|
"ds-cfg-base-dn: dc=new value 1,dc=com",
|
"ds-cfg-base-dn: dc=new value 2,dc=com",
|
"-",
|
"replace: ds-cfg-group-dn",
|
"ds-cfg-group-dn: dc=new value 3,dc=com",
|
"ds-cfg-group-dn: dc=new value 4,dc=com"
|
};
|
|
int result = TestCaseUtils.applyModifications(true, changes);
|
Assert.assertEquals(result, ResultCode.UNWILLING_TO_PERFORM.intValue());
|
} finally {
|
TestCfg.removeConstraint(constraint);
|
deleteSubtreeNoException(TEST_CHILD_1_DN);
|
}
|
}
|
|
/** Add an entry and check its result. */
|
private void addEntry(ResultCode expected, String... lines) throws Exception {
|
Entry entry = TestCaseUtils.makeEntry(lines);
|
AddOperation add = getRootConnection().processAdd(entry);
|
assertEquals(add.getResultCode(), expected, add.getErrorMessage().toString());
|
}
|
|
private void deleteSubtreeNoException(String dn)
|
{
|
try
|
{
|
deleteSubtree(dn);
|
}
|
catch (Exception e)
|
{
|
// Ignore.
|
}
|
}
|
|
/** Deletes the named sub-tree. */
|
private void deleteSubtree(String dn) throws Exception {
|
getAdaptor().deleteSubtree(new LdapName(dn));
|
}
|
|
/** Gets the JNDI connection for the test server instance. */
|
private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
|
if (adaptor == null) {
|
adaptor = JNDIDirContextAdaptor.simpleSSLBind("127.0.0.1", TestCaseUtils
|
.getServerAdminPort(), "cn=directory manager", "password");
|
}
|
return adaptor;
|
}
|
|
/** Gets the named parent configuration. */
|
private TestParentCfg getParent(String name) throws Exception {
|
ServerManagementContext ctx = ServerManagementContext.getInstance();
|
ServerManagedObject<RootCfg> root = ctx.getRootConfigurationManagedObject();
|
return root.getChild(
|
TestCfg.getTestOneToManyParentRelationDefinition(), name)
|
.getConfiguration();
|
}
|
}
|